Нелогичный бенчмаркинг? - PullRequest
0 голосов
/ 23 марта 2009

Я был свидетелем следующего странного поведения. У меня есть две функции, которые делают почти то же самое - они измеряют количество циклов, необходимое для выполнения определенной операции. В одной функции внутри цикла я увеличиваю переменную; в другом ничего не происходит. Переменные изменчивы, поэтому они не будут оптимизированы. Это функции:

unsigned int _osm_iterations=5000;

double osm_operation_time(){
    // volatile is used so that j will not be optimized, and ++ operation
    // will be done in each loop
    volatile unsigned int j=0;
    volatile unsigned int i;
    tsc_counter_t start_t, end_t;
    start_t = tsc_readCycles_C();
    for (i=0; i<_osm_iterations; i++){
       ++j;
    }
    end_t = tsc_readCycles_C();
    if (tsc_C2CI(start_t) ==0 || tsc_C2CI(end_t) ==0 || tsc_C2CI(start_t) >= tsc_C2CI(end_t))
         return -1;
    return (tsc_C2CI(end_t)-tsc_C2CI(start_t))/_osm_iterations;
}

double osm_empty_time(){
    volatile unsigned int i;
    volatile unsigned int j=0;
    tsc_counter_t start_t, end_t;
    start_t = tsc_readCycles_C();
    for (i=0; i<_osm_iterations; i++){
        ;
    }
    end_t = tsc_readCycles_C();
    if (tsc_C2CI(start_t) ==0 || tsc_C2CI(end_t) ==0 || tsc_C2CI(start_t) >= tsc_C2CI(end_t))
        return -1;
    return (tsc_C2CI(end_t)-tsc_C2CI(start_t))/_osm_iterations;
}

Там есть несколько нестандартных функций, но я уверен, что вы справитесь.

Дело в том, что первая функция возвращает 4 , а вторая функция (которая предположительно делает меньше) возвращает 6 , хотя вторая, очевидно, делает меньше, чем первая.

Имеет ли это какой-либо смысл для кого-либо?

На самом деле я выполнил первую функцию, чтобы я мог уменьшить накладные расходы цикла для моего измерения второй. Есть ли у вас какие-либо идеи, как это сделать (поскольку этот метод на самом деле не сокращает его)?

Я в Ubuntu (я думаю, что это 64-битная версия).

Большое спасибо.

Ответы [ 5 ]

4 голосов
/ 23 марта 2009

Я вижу пару вещей здесь. Во-первых, код для двух циклов выглядит одинаково. Во-вторых, компилятор, вероятно, поймет, что переменная i и переменная j всегда будут иметь одно и то же значение, и оптимизирует одно из них. Вы должны посмотреть на сгенерированную сборку и увидеть, что на самом деле происходит.

Другая теория заключается в том, что изменение внутреннего тела цикла повлияло на кэшируемость кода - это могло бы переместить его через строки кэша или что-то еще.

Так как код настолько тривиален, вам может быть трудно получить точное значение времени, даже если вы делаете 5000 итераций, вы можете обнаружить, что время находится внутри поля для ошибки для используемого вами кода времени. Современный компьютер, вероятно, может выполнить это менее чем за миллисекунду - возможно, вам следует увеличить количество итераций?

Чтобы увидеть сгенерированную сборку в gcc, укажите параметр компилятора -S :

В: Как посмотреть на ассемблерный код? генерируется GCC?

В: Как я могу создать файл, где я могу увидеть код С и его сборку перевод вместе?

A: Используйте ключ -S (примечание: заглавная S) в GCC, и он будет излучать сборку код в файл с расширением .s. Например, следующая команда:

gcc -O2 -S -c foo.c

оставит сгенерированный ассемблерный код в файле foo.s.

Если вы хотите увидеть код C вместе со сборкой, в которую он был преобразован, используйте командную строку следующим образом:

gcc -c -g -Wa, -a, -ad [другие GCC options] foo.c> foo.lst

, который выведет комбинированный C / сборка листинга в файл foo.lst.

1 голос
/ 23 марта 2009

Подобные вещи сильно зависят от оптимизации компилятора и разрешения таймера. Предоставленные вами результаты (4 и 6) являются довольно низкими, независимо от единицы измерения. Для правильного бенчмарка вы должны заключить обе эти функции в цикл, который выполняет их несколько тысяч раз.

0 голосов
/ 23 марта 2009

Когда я запускаю тесты, подобные этим, я обычно:

  1. Убедитесь, что время измеряется, по крайней мере, в секундах, предпочтительно (малых) десятках секунд.
  2. Один запуск программы вызовет первую функцию, затем вторую, затем снова первую, затем снова вторую и т. Д., Просто чтобы увидеть, есть ли странные проблемы с прогревом кэша.
  3. Запустите программу несколько раз, чтобы увидеть, насколько стабильно время между запусками.

Я все еще затрудняюсь объяснить ваши наблюдаемые результаты, но если вы уверены, что правильно определили свои функции (само собой разумеется, если учесть, что ранее были ошибки копирования и вставки, для пример), затем, глядя на вывод ассемблера, остается основной параметр.

0 голосов
/ 23 марта 2009

Если вы действительно пытаетесь проверить работу фрагмента кода (в данном случае "j++;"), вам лучше сделать следующее:

1 / Делайте это в двух отдельных исполняемых файлах, поскольку существует вероятность того, что положение в исполняемом файле может повлиять на код.

2 / Убедитесь, что вы используете процессорное время, а не истекшее время (я не уверен, что дает "tsc_readCycles_C()"). Это сделано для того, чтобы избежать ошибочных результатов от процессора, загруженного другими задачами.

3 / Отключите оптимизацию компилятора (например, "gcc -O0"), чтобы убедиться, что gcc не содержит каких-либо необычных вещей, которые могут исказить результаты.

4 / Вам не нужно беспокоиться о volatile, если вы используете фактический результат, такой как размещение:

printf ("%d\n",j);

после цикла или:

FILE *fx = fopen ("/dev/null","w");
fprintf (fx, "%d\n", j);
fclose (fx);

если вы вообще не хотите выводить. Я не могу вспомнить, был ли volatile предложением для компилятора или принудительным.

5 / Итерации по 5000 кажутся немного низкими, где «шум» может повлиять на показания. Может быть, более высокое значение будет лучше. Это может не быть проблемой, если вы рассчитываете больший фрагмент кода и просто включили "j++;" в качестве заполнителя.

0 голосов
/ 23 марта 2009

Иногда трудно догадаться о подобных вещах, особенно из-за небольшого количества итераций. Однако может произойти и то, что приращение может выполняться на модуле исполнения со свободным целым числом, получая некоторую небольшую степень параллелизма, поскольку оно не зависит от значения i.

Поскольку вы упомянули, что это 64-разрядная ОС, почти наверняка все эти значения находятся в регистрах, поскольку в архитектуре x86_64 больше регистров. Кроме этого, я бы сказал, выполнил еще много итераций и увидел бы, насколько стабильны результаты.

...