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

Я портирую игру, которая изначально была написана для 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.

Ответов (10)

Решение

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

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

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

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

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

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

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

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

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

Высокое разрешение, низкие затраты времени для процессоров 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);
}

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

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

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

@ Бернард:

Должен признаться, большинство ваших примеров пролетело мимо меня. Он компилируется и, похоже, работает. Это безопасно для систем 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 ... ассемблер - это то, что вам следует изучить, если вы хотите знать, что делает компьютер!»

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

Возможно, вас заинтересует FAQ по Linux дляclock_gettime(CLOCK_REALTIME)

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

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