Расчет частоты процессора в C с помощью RDTSC всегда возвращает 0
-
26-09-2019 - |
Вопрос
Наш инструктор дал нам следующий фрагмент кода, чтобы мы могли измерить производительность некоторых алгоритмов:
#include <stdio.h>
#include <unistd.h>
static unsigned cyc_hi = 0, cyc_lo = 0;
static void access_counter(unsigned *hi, unsigned *lo) {
asm("rdtsc; movl %%edx,%0; movl %%eax,%1"
: "=r" (*hi), "=r" (*lo)
: /* No input */
: "%edx", "%eax");
}
void start_counter() {
access_counter(&cyc_hi, &cyc_lo);
}
double get_counter() {
unsigned ncyc_hi, ncyc_lo, hi, lo, borrow;
double result;
access_counter(&ncyc_hi, &ncyc_lo);
lo = ncyc_lo - cyc_lo;
borrow = lo > ncyc_lo;
hi = ncyc_hi - cyc_hi - borrow;
result = (double) hi * (1 << 30) * 4 + lo;
return result;
}
Однако мне нужно, чтобы этот код был переносим на машины с разными частотами процессора.Для этого я пытаюсь вычислить частоту процессора машины, на которой выполняется код, следующим образом:
int main(void)
{
double c1, c2;
start_counter();
c1 = get_counter();
sleep(1);
c2 = get_counter();
printf("CPU Frequency: %.1f MHz\n", (c2-c1)/1E6);
printf("CPU Frequency: %.1f GHz\n", (c2-c1)/1E9);
return 0;
}
Проблема в том, что результат всегда 0, и я не могу понять, почему.Я использую Linux (Arch) в качестве гостя на VMware.
На машине друга (MacBook) в некоторой степени работает;То есть результат больше 0, но он переменный, потому что частота процессора не фиксирована (мы пытались это исправить, но по какой-то причине не можем это сделать).У него есть другая машина, на которой в качестве хоста работает Linux (Ubuntu), и она также сообщает 0.Это исключает проблему на виртуальной машине, о которой я сначала подумал.
Есть идеи, почему это происходит и как это исправить?
Решение
Хорошо, поскольку другой ответ не помог, я постараюсь объяснить более подробно.Проблема в том, что современный процессор может выполнять инструкции не по порядку.Ваш код начинается примерно так:
rdtsc
push 1
call sleep
rdtsc
Современные процессоры делают нет однако обязательно выполняйте инструкции в исходном порядке.Несмотря на ваш первоначальный порядок, процессор (в основном) свободен для выполнения этого, например:
rdtsc
rdtsc
push 1
call sleep
В этом случае понятно, почему разница между этими двумя rdtsc
s будет (по крайней мере, очень близко к) 0.Чтобы этого не произошло, вам необходимо выполнить команду, которую процессор будет выполнять. никогда переставить, чтобы выполнить не по порядку.Наиболее распространенная инструкция для этого: CPUID
.Другой ответ, который я связал, должен (если мне не изменяет память) начинаться примерно оттуда, о шагах, необходимых для использования. CPUID
правильно/эффективно для этой задачи.
Конечно, возможно, что Тим Пост был прав, и вы также вижу проблемы из-за виртуальной машины.Тем не менее, в нынешнем виде нет никакой гарантии, что ваш код будет работать корректно даже на реальном оборудовании.
Редактировать:относительно того, почему код бы работа:ну, во-первых, то, что инструкция может быть выполнены вне очереди, не гарантирует, что они воля быть.Во-вторых, возможно (по крайней мере, в некоторых реализациях) sleep
содержат инструкции сериализации, которые предотвращают rdtsc
от перестановки вокруг него, в то время как другие этого не делают (или могут содержать их, но выполняют только при определенных (но неуказанных) обстоятельствах).
У вас остается поведение, которое может измениться практически при любой перекомпиляции или даже между одним запуском и другим.Он мог давать чрезвычайно точные результаты десятки раз подряд, а затем давать сбой по какой-то (почти) совершенно необъяснимой причине (например, что-то, что произошло совершенно в каком-то другом процессе).
Другие советы
Я не могу сказать наверняка, что именно не так с вашим кодом, но для такой простой инструкции вы делаете довольно много лишней работы.Я рекомендую вам упростить rdtsc
код существенно.Вам не нужно выполнять 64-битные математические вычисления самостоятельно, и вам не нужно сохранять результат этой операции как двойной.Вам не нужно использовать отдельные выходные данные во встроенном asm, вы можете указать GCC использовать eax и edx.
Вот сильно упрощенная версия этого кода:
#include <stdint.h>
uint64_t rdtsc() {
uint64_t ret;
# if __WORDSIZE == 64
asm ("rdtsc; shl $32, %%rdx; or %%rdx, %%rax;"
: "=A"(ret)
: /* no input */
: "%edx"
);
#else
asm ("rdtsc"
: "=A"(ret)
);
#endif
return ret;
}
Также вам следует рассмотреть возможность распечатки значений, которые вы получаете из этого, чтобы вы могли видеть, получаете ли вы 0 или что-то еще.
Что касается VMWare, взгляните на спецификация учета времени (ссылка в формате PDF), а также эта тема.Инструкции TSC (в зависимости от гостевой ОС):
- Передается непосредственно на реальное оборудование (гость PV)
- Циклы подсчета пока виртуальная машина выполняется на хост-процессоре (Windows/и т. д.)
Обратите внимание: в № 2 пока виртуальная машина выполняется на хост-процессоре.Если я правильно помню, то же самое можно сказать и о Зене.По сути, вы можете ожидать, что код будет работать должным образом на паравиртуализированной гостевой системе.В случае эмуляции совершенно неразумно ожидать согласованности аппаратного обеспечения.
Вы забыли использовать volatile
в вашем заявлении asm, поэтому вы сообщаете компилятору, что asm
Оператор каждый раз выдает один и тот же результат, как чистая функция.(volatile
является только неявным для asm
операторы без выходных данных.)
Это объясняет, почему вы получаете точно нуль:компилятор оптимизировал end-start
к 0
во время компиляции через CSE (устранение общего подвыражения).
Смотрите мой ответ на Получить количество циклов процессора? для __rdtsc()
встроенный, и в ответе @Mysticial есть работающий встроенный ассемблер GNU C, который я процитирую здесь:
// prefer using the __rdtsc() intrinsic instead of inline asm at all. uint64_t rdtsc(){ unsigned int lo,hi; __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return ((uint64_t)hi << 32) | lo; }
Это работает корректно и эффективно для 32- и 64-битного кода.
хммм, я не уверен, но подозреваю, что проблема может быть внутри этой строки:
результат = (двойной) привет * (1 << 30) * 4 + lo;
Я подозреваю, что вы можете безопасно выполнять такие огромные умножения в «беззнаковом» формате...разве это не 32-битное число?... сам факт, что вы не могли безопасно умножить на 2^32 и вам пришлось добавить его как дополнительный "* 4", добавленный к 2^30 в конце, уже намекает на такую возможность...вам может потребоваться преобразовать каждый подкомпонент hi и lo в двойной (вместо одного в самом конце) и выполнить умножение, используя два двойных значения.