Гарантируется ли разрешение gettimeofday() в микросекундах?
-
08-06-2019 - |
Вопрос
Я портирую игру, которая изначально была написана для Win32 API, на Linux (ну, портирую порт OS X для Win32-порта на Linux).
Я реализовал QueryPerformanceCounter
указав количество использованных секунд с момента запуска процесса:
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 и разделить, чтобы получить количество секунд.Преобразовать это в double довольно удобно.
Когда я запускаю это на своем компьютере, я получаю
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);
}
@Бернард:
Я должен признать, что большая часть вашего примера прошла мимо моей головы.Тем не менее, он компилируется и, похоже, работает.Безопасно ли это для систем SMP или SpeedStep?
Это хороший вопрос...Я думаю, что с кодом все в порядке.С практической точки зрения, мы используем его в моей компании каждый день, и мы работаем на довольно широком спектре устройств, начиная с 2-8 ядер.Конечно, YMMV и т.д., Но это, похоже, надежный и малозатратный (потому что он не переключает контекст в системное пространство) метод синхронизации.
В общем, как это работает, так это:
- объявите блок кода ассемблерным (и изменяемым, поэтому оптимизатор оставит его в покое).
- выполните инструкцию CPUID.В дополнение к получению некоторой информации о процессоре (с которой мы ничего не делаем) он синхронизирует буфер выполнения процессора так что выполнение не по порядку не влияет на тайминги.
- выполните выполнение rdtsc (считывание временной метки).Это позволяет получить количество машинных циклов, выполненных с момента перезагрузки процессора.Это 64-разрядное значение, поэтому при текущих скоростях процессора оно будет изменяться примерно каждые 194 года или около того.Интересно, что в оригинальном справочнике Pentium они отмечают, что он меняется каждые 5800 лет или около того.
- последние две строки сохраняют значения из регистров в переменные hi и lo и помещают их в 64-битное возвращаемое значение.
Конкретные примечания:
выполнение не по порядку может привести к неправильным результатам, поэтому мы выполняем инструкцию "cpuid", которая в дополнение к предоставлению вам некоторой информации о процессоре также синхронизирует любое выполнение команды не по порядку.
Большинство ОС синхронизируют счетчики на процессорах при их запуске, поэтому ответ хорош с точностью до пары наносекунд.
Комментарий о переходе в спящий режим, вероятно, верен, но на практике вас , вероятно, не волнуют тайминги за пределами границ спящего режима.
что касается speedstep:Новые процессоры Intel компенсируют скорость изменяет и возвращает скорректированное количество.Я быстро просмотрел некоторые ящики в нашей сети и нашел только один ящик, в котором этого не было:Pentium 3, на котором работает какой-то старый сервер базы данных.(это коробки Linux, поэтому я проверил с помощью:grep constant_tsc /proc/cpuinfo)
Я не уверен насчет процессоров AMD, мы в основном магазин Intel, хотя я знаю, что некоторые из наших системных гуру низкого уровня проводили оценку AMD.
Надеюсь, это удовлетворит ваше любопытство, это интересная и (ИМХО) недостаточно изученная область программирования.Помните, когда Джефф и Джоэл говорили о том, должен ли программист знать C?Я кричал им: "Эй, забудьте об этой фигне с высоким уровнем C...ассемблер это то, что вам следует изучить, если вы хотите знать, что делает компьютер !"
Возможно, вас заинтересует Часто задаваемые вопросы по Linux для 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)
в вашей системе, чтобы увидеть, каково фактическое разрешение.