Тестирование ассемблера на Intel с использованием rdtsc дает странные ответы, почему? - PullRequest
0 голосов
/ 23 сентября 2018

Некоторое время назад я задал вопрос о переполнении стека и мне показали, как выполнить код операции rdtsc в C ++.Недавно я создал эталонную функцию с использованием rdtsc следующим образом:

inline unsigned long long rdtsc() {
  unsigned int lo, hi;
  asm volatile (
     "cpuid \n"
     "rdtsc" 
   : "=a"(lo), "=d"(hi) /* outputs */
   : "a"(0)             /* inputs */
   : "%ebx", "%ecx");     /* clobbers*/
  return ((unsigned long long)lo) | (((unsigned long long)hi) << 32);
}

typedef uint64_t (*FuncOneInt)(uint32_t n);
/**
     time a function that takes an integer parameter and returns a 64 bit number
     Since this is capable of timing in clock cycles, we won't have to do it a
     huge number of times and divide, we can literally count clocks.
     Don't forget that everything takes time including getting into and out of the
     function.  You may want to time an empty function.  The time to do the computation
     can be compute by taking the time of the function you want minus the empty one.
 */
void clockBench(const char* msg, uint32_t n, FuncOneInt f) {
    uint64_t t0 = rdtsc();
    uint64_t r = f(n);
    uint64_t t1 = rdtsc();
    std::cout << msg << "n=" << n << "\telapsed=" << (t1-t0) << '\n';
}

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

Вотпример:

uint64_t empty(uint32_t n) {
    return 0;
}

uint64_t sum1Ton(uint32_t n) {
    uint64_t s = 0;
    for (int i = 1; i <= n; i++)
        s += i;
    return s;
}

Код скомпилирован с использованием

g++ -g -O2

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

empty n=100 elapsed=438
Sum 1 to n=100  elapsed=887

empty n=100 elapsed=357
Sum 1 to n=100  elapsed=347

Постоянно пустая функция показывает, что она занимает намного больше времени, чем должна.

В конце концов, есть только несколькоинструкции по входу и выходу из функции.Настоящая работа выполняется в цикле.Не берите в голову тот факт, что разница огромна.Во втором запуске пустая функция утверждает, что она берет 357 тактов, а сумма занимает меньше, что смешно.

Что происходит?

1 Ответ

0 голосов
/ 23 сентября 2018

Постоянно пустая функция показывает, что она занимает намного больше времени, чем должна.

У вас есть cpuid внутри временного интервала .cpuid на процессорах семейства Intel Sandybridge занимает от 100 до 250 тактов ядра (в зависимости от входов, которые вы не указали), согласно тестированию Agner Fog.(https://agner.org/optimize/).

Но вы измеряете не тактовые циклы ядра, а эталонные циклы RDTSC, которые могут быть значительно короче. (Например, мой Skylake i7-6700k на холостом ходу с частотой 800 МГц, но частота эталонного тактового сигнала4008 МГц.) См. Получить счетчик циклов ЦП? для моей попытки канонического ответа на rdtsc.

Сначала прогрейте ЦП или запустите pause занятый цикл наеще одно ядро, поддерживающее максимальную привязку (при условии, что это двухъядерный или четырехъядерный процессор для настольного компьютера / ноутбука, где все частоты ядра соединены вместе).


Не берите в голову тот факт, что разница огромнаВо втором запуске пустая функция утверждает, что она берет 357 тактов, а сумма занимает меньше, что смешно.

Является ли , что эффект также согласован?

Может быть, ваш процессор набрал полную скорость во время / после печати 3-й строки сообщений, что заставляет последний синхронизированный регион работать намного быстрее? ( Почему этот цикл задержки начинает работать быстрее после нескольких итераций безSLEep? ).

IDK, какой эффект мог бы оказать различный мусор в eax и ecx до того, как cpuid мог бы это сделать.Замените его на lfence, чтобы устранить это, и используйте намного меньшие накладные расходы для сериализации rdtsc.

...