Реализация портативного решения
Поскольку здесь уже упоминалось, что не существует надлежащего решения ANSI с достаточной точностью для задачи измерения времени, я хочу написать о том, как получить портативное и, если возможно, решение для измерения времени с высоким разрешением.
Монотонные часы против отметок времени
Вообще говоря, есть два способа измерения времени:
- монотонные часы;
- текущая (дата) отметка времени.
Первый использует монотонный счетчик часов (иногда он называется счетчиком тиков), который подсчитывает тики с заранее определенной частотой, поэтому, если у вас есть значение тиков и частота известна, вы можете легко преобразовать тики в истекшее время. Фактически не гарантируется, что монотонные часы каким-либо образом отражают текущее системное время, они также могут считать тики с момента запуска системы. Но это гарантирует, что часы всегда будут увеличиваться независимо от состояния системы. Обычно частота связана с аппаратным источником высокого разрешения, поэтому она обеспечивает высокую точность (зависит от аппаратного обеспечения, но у большинства современного оборудования нет проблем с источниками тактового сигнала с высоким разрешением).
Второй способ предоставляет значение времени (даты), основанное на текущем значении системных часов. Он также может иметь высокое разрешение, но у него есть один существенный недостаток: на этот тип времени могут влиять различные настройки системного времени, например, изменение часового пояса, изменение летнего времени (DST), обновление NTP-сервера, спящий режим системы и т. Д. на. В некоторых случаях вы можете получить отрицательное значение прошедшего времени, которое может привести к неопределенному поведению. На самом деле этот источник времени менее надежен, чем первый.
Итак, первое правило при измерении временного интервала - это использовать монотонные часы, если это возможно. Он обычно имеет высокую точность и надежен по конструкции.
Стратегия отступления
При реализации переносимого решения стоит рассмотреть запасную стратегию: использовать монотонные часы, если они доступны, и подход с использованием запасных меток времени, если в системе нет монотонных часов.
Windows
Существует замечательная статья под названием Получение отметок времени с высоким разрешением на MSDN об измерении времени в Windows, в которой описываются все детали, которые вам могут понадобиться о поддержке программного и аппаратного обеспечения. Чтобы получить метку времени высокой точности в Windows, вам необходимо:
запросить частоту таймера (тиков в секунду) с помощью QueryPerformanceFrequency :
LARGE_INTEGER tcounter;
LARGE_INTEGER freq;
if (QueryPerformanceFrequency (&tcounter) != 0)
freq = tcounter.QuadPart;
Частота таймера фиксируется при загрузке системы, поэтому вам нужно получить ее только один раз.
запросить текущее значение тиков с помощью QueryPerformanceCounter :
LARGE_INTEGER tcounter;
LARGE_INTEGER tick_value;
if (QueryPerformanceCounter (&tcounter) != 0)
tick_value = tcounter.QuadPart;
масштабировать тики до истекшего времени, то есть до микросекунд:
LARGE_INTEGER usecs = (tick_value - prev_tick_value) / (freq / 1000000);
Согласно Microsoft, в большинстве случаев у вас не должно быть проблем с этим подходом в Windows XP и более поздних версиях. Но вы также можете использовать два запасных решения для Windows:
- GetTickCount показывает количество миллисекунд, прошедших с момента запуска системы. Он оборачивается каждые 49,7 дня, поэтому будьте осторожны при измерении более длительных интервалов.
- GetTickCount64 - это 64-разрядная версия
GetTickCount
, но она доступна начиная с Windows Vista и выше.
OS X (macOS)
OS X (macOS) имеет собственные единицы абсолютного времени Маха, которые представляют собой монотонные часы. Лучший способ начать - статья Apple Технические вопросы и ответы QA1398: Единицы абсолютного времени Маха , в которой описывается (с примерами кода), как использовать API, специфичный для Маха, для получения монотонных тиков. Существует также локальный вопрос об этом, называемый clock_gettime альтернатива в Mac OS X , который в конце может оставить вас в замешательстве, что делать с возможным переполнением значения, потому что частота счетчика используется в виде числителя и знаменатель. Итак, короткий пример, как получить истекшее время:
получить числитель тактовой частоты и знаменатель:
#include <mach/mach_time.h>
#include <stdint.h>
static uint64_t freq_num = 0;
static uint64_t freq_denom = 0;
void init_clock_frequency ()
{
mach_timebase_info_data_t tb;
if (mach_timebase_info (&tb) == KERN_SUCCESS && tb.denom != 0) {
freq_num = (uint64_t) tb.numer;
freq_denom = (uint64_t) tb.denom;
}
}
Вам нужно сделать это только один раз.
запросить текущее значение тика с помощью mach_absolute_time
:
uint64_t tick_value = mach_absolute_time ();
масштабировать тики до истекшего времени, то есть до микросекунд, используя ранее запрошенные числитель и знаменатель:
uint64_t value_diff = tick_value - prev_tick_value;
/* To prevent overflow */
value_diff /= 1000;
value_diff *= freq_num;
value_diff /= freq_denom;
Основной идеей предотвращения переполнения является уменьшение тиков до желаемой точности перед использованием числителя и знаменателя. Поскольку начальное разрешение таймера в наносекундах, мы делим его на 1000
, чтобы получить микросекунды. Вы можете найти тот же подход, который использовался в time_mac.c Chromium. Если вам действительно нужна точность в наносекунды, подумайте о прочтении Как я могу использовать mach_absolute_time без переполнения? .
Linux и UNIX
Вызов clock_gettime
- ваш лучший способ в любой POSIX-дружественной системе. Он может запрашивать время из разных источников часов, и нам нужен CLOCK_MONOTONIC
. Не все системы, поддерживающие clock_gettime
, поддерживают CLOCK_MONOTONIC
, поэтому первое, что вам нужно сделать, это проверить их доступность:
- если для
_POSIX_MONOTONIC_CLOCK
задано значение >= 0
, это означает, что CLOCK_MONOTONIC
доступно;
если _POSIX_MONOTONIC_CLOCK
определено для 0
, это означает, что вам следует дополнительно проверить, работает ли оно во время выполнения, я предлагаю использовать sysconf
:
#include <unistd.h>
#ifdef _SC_MONOTONIC_CLOCK
if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
/* A monotonic clock presents */
}
#endif
- в противном случае монотонные часы не поддерживаются, и вам следует использовать запасную стратегию (см. Ниже).
Использование clock_gettime
довольно просто:
получить значение времени:
#include <time.h>
#include <sys/time.h>
#include <stdint.h>
uint64_t get_posix_clock_time ()
{
struct timespec ts;
if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
return (uint64_t) (ts.tv_sec * 1000000 + ts.tv_nsec / 1000);
else
return 0;
}
Я сократил время до микросекунд.
рассчитать разницу с предыдущим значением времени, полученным таким же образом:
uint64_t prev_time_value, time_value;
uint64_t time_diff;
/* Initial time */
prev_time_value = get_posix_clock_time ();
/* Do some work here */
/* Final time */
time_value = get_posix_clock_time ();
/* Time difference */
time_diff = time_value - prev_time_value;
Лучшая резервная стратегия - использовать вызов gettimeofday
: он не монотонный, но обеспечивает довольно хорошее разрешение. Идея та же, что и у clock_gettime
, но чтобы получить значение времени, вы должны:
#include <time.h>
#include <sys/time.h>
#include <stdint.h>
uint64_t get_gtod_clock_time ()
{
struct timeval tv;
if (gettimeofday (&tv, NULL) == 0)
return (uint64_t) (tv.tv_sec * 1000000 + tv.tv_usec);
else
return 0;
}
Опять же, значение времени уменьшается до микросекунд.
SGI IRIX
IRIX имеет вызов clock_gettime
, но ему не хватает CLOCK_MONOTONIC
. Вместо этого он имеет свой собственный источник монотонных часов, определенный как CLOCK_SGI_CYCLE
, который вы должны использовать вместо CLOCK_MONOTONIC
с clock_gettime
.
Solaris и HP-UX
Solaris имеет собственный интерфейс таймера высокого разрешения gethrtime
, который возвращает текущее значение таймера в наносекундах. Хотя более новые версии Solaris могут иметь clock_gettime
, вы можете придерживаться gethrtime
, если вам нужно поддерживать старые версии Solaris.
Простое использование:
#include <sys/time.h>
void time_measure_example ()
{
hrtime_t prev_time_value, time_value;
hrtime_t time_diff;
/* Initial time */
prev_time_value = gethrtime ();
/* Do some work here */
/* Final time */
time_value = gethrtime ();
/* Time difference */
time_diff = time_value - prev_time_value;
}
HP-UX не хватает clock_gettime
, но он поддерживает gethrtime
, который следует использовать так же, как и в Solaris.
BeOS
BeOS также имеет собственный интерфейс таймера высокого разрешения system_time
, который возвращает количество микросекунд, прошедших с момента загрузки компьютера.
Пример использования:
#include <kernel/OS.h>
void time_measure_example ()
{
bigtime_t prev_time_value, time_value;
bigtime_t time_diff;
/* Initial time */
prev_time_value = system_time ();
/* Do some work here */
/* Final time */
time_value = system_time ();
/* Time difference */
time_diff = time_value - prev_time_value;
}
OS / 2
OS / 2 имеет собственный API для получения меток времени высокой точности:
запросить частоту таймера (тиков на единицу) с помощью DosTmrQueryFreq
(для компилятора GCC):
#define INCL_DOSPROFILE
#define INCL_DOSERRORS
#include <os2.h>
#include <stdint.h>
ULONG freq;
DosTmrQueryFreq (&freq);
запросить текущее значение тиков с помощью DosTmrQueryTime
:
QWORD tcounter;
unit64_t time_low;
unit64_t time_high;
unit64_t timestamp;
if (DosTmrQueryTime (&tcounter) == NO_ERROR) {
time_low = (unit64_t) tcounter.ulLo;
time_high = (unit64_t) tcounter.ulHi;
timestamp = (time_high << 32) | time_low;
}
масштабировать тики до истекшего времени, то есть до микросекунд:
uint64_t usecs = (prev_timestamp - timestamp) / (freq / 1000000);
Пример реализации
Вы можете взглянуть на библиотеку plibsys , которая реализует все описанные выше стратегии (см. Ptimeprofiler * .c для получения подробной информации).