Я портирую игру, изначально написанную для Win32 API, на Linux (ну, портируя порт OS X порта Win32 на Linux).
Я реализовал QueryPerformanceCounter
, давая uSeconds с момента запуска процесса:
BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
gettimeofday(¤tTimeVal, NULL);
performanceCount->QuadPart = (currentTimeVal.tv_sec - startTimeVal.tv_sec);
performanceCount->QuadPart *= (1000 * 1000);
performanceCount->QuadPart += (currentTimeVal.tv_usec - startTimeVal.tv_usec);
return true;
}
Это, вместе с QueryPerformanceFrequency()
присвоением постоянной 1000000 в качестве частоты, хорошо работает на моей машине , давая мне 64-битную переменную, которая содержится uSeconds
с момента запуска программы.
Так это портативный? Я не хочу обнаружить, что это работает по-другому, если ядро было скомпилировано определенным образом или что-то в этом роде. Тем не менее, я в порядке, поскольку он не переносим для чего-то другого, кроме Linux.
Может быть. Но у вас есть большие проблемы. gettimeofday()
может привести к неправильной синхронизации, если в вашей системе есть процессы, которые изменяют таймер (например, ntpd). На «нормальном» Linux, однако, я считаю, что разрешение gettimeofday()
составляет 10us. Следовательно, он может прыгать вперед и назад и время, в зависимости от процессов, запущенных в вашей системе. Это эффективно делает ответ на ваш вопрос нет.
Вы должны посмотреть на clock_gettime(CLOCK_MONOTONIC)
временные интервалы. Он страдает от нескольких меньших проблем из-за таких вещей, как многоядерные системы и внешние настройки часов.
Кроме того, посмотрите на clock_getres()
функцию.
Высокое разрешение и низкая нагрузка на процессоры Intel
Если вы используете аппаратное обеспечение Intel, вот как прочитать счетчик команд процессора в режиме реального времени. Он сообщит вам количество циклов ЦП, выполненных с момента загрузки процессора. Это, вероятно, самый точный счетчик, который вы можете получить для измерения производительности.
Обратите внимание, что это количество циклов ЦП. В Linux вы можете получить скорость процессора из / proc / cpuinfo и разделить, чтобы получить количество секунд. Преобразование этого в двойной довольно удобно.
Когда я запускаю это на своей коробке, я получаю
11867927879484732
11867927879692217
it took this long to call printf: 207485
Вот руководство Intel для разработчиков, которое дает массу деталей.
#include <stdio.h>
#include <stdint.h>
inline uint64_t rdtsc() {
uint32_t lo, hi;
__asm__ __volatile__ (
"xorl %%eax, %%eax\n"
"cpuid\n"
"rdtsc\n"
: "=a" (lo), "=d" (hi)
:
: "%ebx", "%ecx");
return (uint64_t)hi << 32 | lo;
}
main()
{
unsigned long long x;
unsigned long long y;
x = rdtsc();
printf("%lld\n",x);
y = rdtsc();
printf("%lld\n",y);
printf("it took this long to call printf: %lld\n",y-x);
}
CPUID
снова использоваться после первой RDTSC
инструкции и перед выполнением тестируемого кода? Иначе, что мешает выполнению сравнительного кода, выполняемого до / параллельно с первым RDTSC
, и, следовательно, недопредставленного в RDTSC
дельте? @Bernard:
Должен признаться, большая часть вашего примера прошла прямо над моей головой. Он компилируется и, похоже, работает. Это безопасно для систем SMP или SpeedStep?
Это хороший вопрос ... Я думаю, что код в порядке. С практической точки зрения, мы используем его в своей компании каждый день, и мы работаем на довольно широком спектре коробок, все от 2-8 ядер. Конечно, YMMV и т. Д., Но, похоже, это надежный метод синхронизации с низкими издержками (потому что он не переключает контекст в системное пространство).
Вообще, как это работает:
- объявите блок кода ассемблером (и volatile, чтобы оптимизатор оставил его в покое).
- выполнить инструкцию CPUID. В дополнение к получению некоторой информации о процессоре (с которой мы ничего не делаем) он синхронизирует буфер выполнения ЦП, чтобы на время не влияло неупорядоченное выполнение.
- выполнить выполнение rdtsc (read timestamp). Это выбирает количество машинных циклов, выполненных с момента сброса процессора. Это 64-битное значение, поэтому при текущей скорости процессора оно будет изменяться каждые 194 года или около того. Интересно, что в оригинальном справочнике по Pentium они отмечают, что он появляется примерно каждые 5800 лет или около того.
- последняя пара строк хранит значения из регистров в переменных hi и lo и помещает их в 64-битное возвращаемое значение.
Конкретные примечания:
неупорядоченное выполнение может привести к неверным результатам, поэтому мы выполняем инструкцию «cpuid», которая помимо предоставления вам некоторой информации о процессоре, также синхронизирует выполнение любых неупорядоченных команд.
Большинство ОС синхронизируют счетчики на процессорах при запуске, поэтому ответ будет хорошим с точностью до пары нано-секунд.
Комментарий к гибернации, вероятно, правдив, но на практике вам, вероятно, не безразлично время выхода за границы гибернации.
относительно скорости: новые процессоры Intel компенсируют изменения скорости и возвращают скорректированное количество. Я быстро просмотрел некоторые блоки в нашей сети и обнаружил только один ящик, в котором его не было: Pentium 3, на котором работал какой-то старый сервер базы данных. (это Linux-боксы, поэтому я проверил: grep constant_tsc / proc / cpuinfo)
Я не уверен насчет процессоров AMD, мы, прежде всего, магазин Intel, хотя я знаю, что некоторые наши гуру систем низкого уровня провели оценку AMD.
Надеюсь, что это удовлетворит ваше любопытство, это интересная и (ИМХО) мало изученная область программирования. Вы знаете, когда Джефф и Джоэл говорили о том, должен ли программист знать C? Я кричал им: «Эй, забудь, что такое высокоуровневое С… ассемблер - это то, что ты должен изучить, если хочешь знать, что делает компьютер!»
Вы можете быть заинтересованы в Linux FAQ дляclock_gettime(CLOCK_REALTIME)
Wine фактически использует gettimeofday () для реализации QueryPerformanceCounter (), и известно, что многие игры для Windows работают на Linux и Mac.
Запускает http://source.winehq.org/source/dlls/kernel32/cpu.c#L312
приводит к http://source.winehq.org/source/dlls/ntdll/time.c#L448
Таким образом, он явно говорит о микросекундах, но говорит о том, что разрешение системных часов не указано. Я полагаю, что разрешение в этом контексте означает, как наименьшее количество будет увеличено?
Структура данных определяется как имеющая микросекунды в качестве единицы измерения, но это не означает, что часы или операционная система действительно способны измерять это точно.
Как и предполагали другие, gettimeofday()
это плохо, потому что установка времени может привести к перекосу часов и сбить ваши расчеты. clock_gettime(CLOCK_MONOTONIC)
это то, что вы хотите, и clock_getres()
скажет вам точность ваших часов.
Фактическое разрешение gettimeofday () зависит от аппаратной архитектуры. Процессоры Intel, а также машины SPARC предлагают таймеры высокого разрешения, которые измеряют микросекунды. Другие аппаратные архитектуры используют системный таймер, который обычно устанавливается на 100 Гц. В таких случаях разрешение по времени будет менее точным.
Я получил этот ответ из «Измерения времени с высоким разрешением и таймеров», часть I
В этом ответе упоминаются проблемы с настройкой часов. Ваши проблемы с гарантией единиц измерения и проблемы с настраиваемым временем решаются в C ++ 11 с помощью <chrono>
библиотеки.
std::chrono::steady_clock
Гарантируется, что часы не будут настроены, и, кроме того, они будут двигаться с постоянной скоростью относительно реального времени, поэтому такие технологии, как SpeedStep, не должны влиять на него.
Вы можете получить безопасные единицы, преобразовав их в одну из std::chrono::duration
специализаций, например std::chrono::microseconds
. С этим типом нет никакой двусмысленности относительно единиц, используемых значением тика. Однако имейте в виду, что часы не обязательно имеют это разрешение. Вы можете преобразовать длительность в аттосекунды, не имея точных часов.
Исходя из моего опыта и из того, что я прочитал через Интернет, ответ «Нет» не гарантируется. Это зависит от скорости процессора, операционной системы, разновидности Linux и т. Д.
Чтение RDTSC не является надежным в системах SMP, поскольку каждый ЦП поддерживает свой собственный счетчик, и каждый счетчик не гарантируется синхронизацией по отношению к другому ЦП.
Я мог бы предложить попробовать clock_gettime(CLOCK_REALTIME)
. В руководстве posix указано, что это должно быть реализовано на всех совместимых системах. Он может обеспечить подсчет наносекунд, но вы, вероятно, захотите проверить clock_getres(CLOCK_REALTIME)
свою систему, чтобы увидеть, каково реальное разрешение.
clock_getres(CLOCK_REALTIME)
не даст реального разрешения. Он всегда возвращает «1 нс» (одну наносекунду), когда доступны hrtimers, проверьте include/linux/hrtimer.h
файл define HIGH_RES_NSEC 1
(подробнее на stackoverflow.com/a/23044075/196561 )