Тестирование ассемблера на Intel с использованием rdtsc дает странные ответы, почему?
Некоторое время назад я задал вопрос о переполнении стека и показал, как выполнить код операции 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 ответ
Постоянно пустая функция показывает, что она занимает намного больше времени, чем должна.
У тебя есть cpuid
внутри временного интервала.cpuid
в процессорах семейства Intel Sandybridge от 100 до 250 тактов ядра (в зависимости от входов, которые вы не указали), согласно тестированию Agner Fog. ( https://agner.org/optimize/).
Но вы измеряете не тактовые циклы ядра, а эталонные циклы RDTSC, которые могут быть значительно короче. (Например, мой Skylake i7-6700k работает на частоте 800 МГц, но эталонная тактовая частота равна 4008 МГц.) См. Получить количество циклов ЦП? за мою попытку канонического ответа наrdtsc
,
Сначала прогрейте процессор или запуститеpause
цикл занятости на другом ядре, чтобы поддерживать максимальную привязку (при условии, что это настольный компьютер / ноутбук с двумя или четырьмя ядрами, где все частоты ядра соединены вместе).
Не берите в голову тот факт, что разница огромна. Во втором запуске пустая функция утверждает, что она берет 357 тактов, а сумма занимает меньше, что смешно.
Этот эффект также соответствует?
Может быть, ваш процессор набрал полную скорость во время / после печати 3-й строки сообщений, что заставило последний регион работать намного быстрее? ( Почему этот цикл задержки начинает работать быстрее после нескольких итераций без сна?).
ИДК как сильно влияют разные фигни в eax и ecx перед cpuid
мог бы иметь. Замени это lfence
чтобы устранить это и использовать гораздо меньше накладных расходов для сериализации rdtsc
,