Гарантировано ли разрешение gettimeofday () в микросекундах?

97

Я портирую игру, которая изначально была написана для Win32 API, в Linux (ну, портирую OS X порта Win32 в Linux).

Я реализовал QueryPerformanceCounter, указав uSeconds с момента запуска процесса:

BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
    gettimeofday(&currentTimeVal, 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.

Бернар
источник

Ответы:

57

Может быть. Но у тебя проблемы посерьезнее. gettimeofday()может привести к неправильному таймингу, если в вашей системе есть процессы, которые изменяют таймер (например, ntpd). Однако на «нормальном» Linux разрешение gettimeofday()составляет 10 мкс. Он может перемещаться вперед и назад и во времени, следовательно, в зависимости от процессов, запущенных в вашей системе. Это фактически дает ответ на ваш вопрос нет.

Вам следует обратить внимание на clock_gettime(CLOCK_MONOTONIC)временные интервалы. Он страдает от нескольких меньших проблем из-за таких вещей, как многоядерные системы и настройки внешних часов.

Также посмотрите на clock_getres()функцию.

Луи Бренди
источник
1
clock_gettime присутствует только в новейшем Linux. в другой системе есть только gettimeofday ()
vitaly.v.ch
3
@ vitaly.v.ch это POSIX, так что это не только Linux и «newist»? даже "Enterprise" дистрибутивы, такие как Red Hat Enterprise Linux, основаны на версии 2.6.18, в которой есть clock_gettime, так что нет, не очень новый ... (дата man-страницы в RHEL - 2004-март-12, так что это уже давно), если вы не говоря о ДЕЙСТВИТЕЛЬНО ЛОМАННЫХ СТАРОХ ЯДРАХ, ты о чём?
Spudd86
clock_gettime был включен в POSIX в 2001 году. Насколько мне известно, в настоящее время clock_gettime () реализовано в Linux 2.6 и qnx. но Linux 2.4 в настоящее время используется во многих производственных системах.
vitaly.v.ch
Он был представлен в 2001 году, но не был обязательным до POSIX 2008.
R .. GitHub ПРЕКРАТИТЕ ПОМОЩЬ ICE
2
Из Linux FAQ для lock_gettime (см. Ответ Дэвида Шлоснагла) «CLOCK_MONOTONIC ... частота настраивается NTP с помощью adjtimex (). В будущем (я все еще пытаюсь установить патч) будет CLOCK_MONOTONIC_RAW, который не будет могут быть изменены и будут иметь линейную корреляцию с аппаратными счетчиками ". Я не думаю, что часы _RAW когда-либо попали в ядро ​​(если только они не были переименованы в _HR, но мои исследования показывают, что усилия также были оставлены).
Тони Делрой
41

Высокое разрешение, низкие затраты времени для процессоров 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);
}
Марк Харрисон
источник
11
Обратите внимание, что TSC не всегда может быть синхронизирован между ядрами, может останавливаться или изменять свою частоту, когда процессор переходит в режимы пониженного энергопотребления (и у вас нет возможности узнать об этом), и в целом он не всегда надежен. Ядро способно определять, когда оно надежно, обнаруживать другие альтернативы, такие как таймер HPET и ACPI PM, и автоматически выбирать лучший из них. Рекомендуется всегда использовать ядро ​​для измерения времени, если вы не уверены, что TSC стабилен и монотонен.
CesarB 07
12
TSC на платформах Intel Core и выше синхронизируется между несколькими процессорами и увеличивается с постоянной частотой независимо от состояний управления питанием. См. Руководство разработчика программного обеспечения Intel, Vol. 3 Раздел 18.10. Однако скорость, с которой увеличивается счетчик, не совпадает с частотой процессора. TSC увеличивается на «максимальной разрешенной частоте платформы, которая равна произведению масштабируемой частоты шины и максимального разрешенного коэффициента шины» Intel Software Developer's Manual, Vol. 3 Раздел 18.18.5. Вы получаете эти значения из регистров модели процессора (MSR).
sstock
7
Вы можете получить масштабируемую частоту шины и максимальное разрешенное соотношение шины, запросив регистры модели ЦП (MSR) следующим образом: Масштабируемая частота шины == MSR_FSB_FREQ [2: 0] id 0xCD, Максимальное разрешенное соотношение шины == MSR_PLATFORM_ID [12: 8] идентификатор 0x17. Обратитесь к Intel SDM Vol.3, приложение B.1, чтобы интерпретировать значения регистров. Вы можете использовать msr-tools в Linux для запроса регистров. kernel.org/pub/linux/utils/cpu/msr-tools
sstock,
1
Разве ваш код не должен использоваться CPUIDснова после первой RDTSCинструкции и перед выполнением тестируемого кода? В противном случае, что помешает выполнению тестируемого кода до / параллельно с первым RDTSCи, следовательно, недопредставленного в RDTSCдельте?
Тони Делрой
18

@ Бернард:

Должен признать, большая часть вашего примера пролетела мне через голову. Он компилируется и, похоже, работает. Это безопасно для систем SMP или SpeedStep?

Хороший вопрос ... Думаю, код в порядке. С практической точки зрения, мы используем его в моей компании каждый день, и мы работаем на довольно большом количестве компьютеров, начиная с 2-8 ядер. Конечно, YMMV и т. Д., Но это кажется надежным и малозатратным (потому что он не переключает контекст в системное пространство) методом синхронизации.

Обычно это работает:

  • объявить блок кода ассемблерным (и изменчивым, поэтому оптимизатор оставит его в покое).
  • выполнить инструкцию CPUID. Помимо получения некоторой информации о ЦП (с которой мы ничего не делаем), он синхронизирует буфер выполнения ЦП, чтобы на тайминги не влияло выполнение вне очереди.
  • выполнить выполнение rdtsc (метка времени чтения). Это извлекает количество машинных циклов, выполненных с момента перезагрузки процессора. Это 64-битное значение, поэтому при текущей скорости ЦП оно будет меняться каждые 194 года или около того. Интересно, что в оригинальном описании Pentium они отмечают, что он обновляется примерно каждые 5800 лет или около того.
  • последние несколько строк сохраняют значения из регистров в переменных hi и lo и помещают их в 64-битное возвращаемое значение.

Конкретные примечания:

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

  • Большинство ОС синхронизируют счетчики на процессорах при запуске, поэтому ответ будет правильным с точностью до пары наносекунд.

  • Комментарий о гибернации, вероятно, верен, но на практике вы, вероятно, не заботитесь о времени перехода через границы спящего режима.

  • относительно скорости: более новые процессоры Intel компенсируют изменения скорости и возвращают скорректированный счетчик. Я быстро просмотрел некоторые коробки в нашей сети и обнаружил только одну коробку, на которой этого не было: Pentium 3, на котором запущен какой-то старый сервер базы данных. (это ящики Linux, поэтому я проверил: grep constant_tsc / proc / cpuinfo)

  • Я не уверен насчет процессоров AMD, мы в первую очередь магазин Intel, хотя я знаю, что некоторые из наших системных гуру низкого уровня проводили оценку AMD.

Надеюсь, это удовлетворит ваше любопытство, это интересная и (ИМХО) недостаточно изученная область программирования. Вы знаете, когда Джефф и Джоэл говорили о том, должен ли программист знать C? Я кричал им: «Эй, забудьте про эти высокоуровневые C ... ассемблер - это то, что вам следует изучить, если вы хотите знать, что делает компьютер!»

Марк Харрисон
источник
1
... Специалисты по ядру пытались убедить людей прекратить использовать rdtsc на некоторое время ... и вообще избегать его использования в ядре, потому что он настолько ненадежен.
Spudd86
1
Для справки, вопрос, который я задал (в отдельном ответе - перед комментариями), был: «Я должен признать, что большая часть вашего примера пролетела мне прямо в голове. Он компилируется и, похоже, работает. Это безопасно для Системы SMP или SpeedStep? "
Бернард
9

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

Структура данных определяется как имеющая микросекунды в качестве единицы измерения, но это не означает, что часы или операционная система действительно способны измерять это точно.

Как предполагали другие люди, gettimeofday()это плохо, потому что установка времени может вызвать смещение часов и сбить ваши вычисления. clock_gettime(CLOCK_MONOTONIC)это то, что вы хотите, и он clock_getres()покажет вам точность ваших часов.

Джо Шоу
источник
Так что же происходит в вашем коде, когда gettimeofday () перескакивает вперед или назад с переходом на летнее время?
mpez0
3
clock_gettime присутствует только в новейшем Linux. в другой системе есть только gettimeofday ()
vitaly.v.ch
8

Фактическое разрешение gettimeofday () зависит от архитектуры оборудования. Процессоры Intel, а также машины SPARC предлагают таймеры с высоким разрешением, измеряющие микросекунды. В других аппаратных архитектурах используется таймер системы, который обычно установлен на 100 Гц. В таких случаях разрешение по времени будет менее точным.

Я получил этот ответ от High Resolution Time Measurement and Timers, Part I

CodingWithoutComments
источник
6

В этом ответе упоминаются проблемы с настройкой часов. И ваши проблемы с гарантией тиков, и проблемы с корректировкой времени решаются в C ++ 11 с помощью <chrono>библиотеки.

std::chrono::steady_clockГарантируется, что часы не будут регулироваться, и, кроме того, они будут двигаться с постоянной скоростью относительно реального времени, поэтому такие технологии, как SpeedStep, не должны влиять на него.

Вы можете получить типизированные единицы, преобразовав в одну из std::chrono::durationспециализаций, например std::chrono::microseconds. С этим типом нет двусмысленности относительно единиц, используемых значением тика. Однако имейте в виду, что часы не обязательно имеют это разрешение. Вы можете преобразовать продолжительность в аттосекунды, даже не имея точных часов.

bames53
источник
4

Исходя из моего опыта и того, что я читал в Интернете, ответ - «Нет», это не гарантируется. Это зависит от скорости процессора, операционной системы, версии Linux и т. Д.

CodingWithoutComments
источник
3

Чтение RDTSC ненадежно в системах SMP, так как каждый ЦП поддерживает свой собственный счетчик, и синхронизация каждого счетчика с другим ЦП не гарантируется.

Я могу предложить попробовать clock_gettime(CLOCK_REALTIME). В руководстве по posix указано, что это должно быть реализовано во всех совместимых системах. Он может обеспечивать счетчик наносекунд, но вы, вероятно, захотите проверить clock_getres(CLOCK_REALTIME)свою систему, чтобы узнать фактическое разрешение.

Дуг
источник
clock_getres(CLOCK_REALTIME)не даст реального разрешения. Он всегда возвращает «1 нс» (одну наносекунду), когда доступны таймеры, проверьте include/linux/hrtimer.hфайл define HIGH_RES_NSEC 1(подробнее на stackoverflow.com/a/23044075/196561 )
osgx