Как измерить время в миллисекундах с использованием ANSI C?

Используя только ANSI C, есть ли способ измерения времени с точностью до миллисекунды или более? Я просматривал time.h, но я нашел только вторую функцию точности.

Нет функции ANSI C, которая обеспечивает лучшее разрешение более 1 секунды, но функция POSIX gettimeofday обеспечивает разрешение в микросекундах. Функция часов измеряет только время, затраченное на выполнение процесса, и не является точным во многих системах.

Вы можете использовать эту функцию следующим образом:

 struct timeval tval_before, tval_after, tval_result; gettimeofday(&tval_before, NULL); // Some code you want to time, for example: sleep(1); gettimeofday(&tval_after, NULL); timersub(&tval_after, &tval_before, &tval_result); printf("Time elapsed: %ld.%06ld\n", (long int)tval_result.tv_sec, (long int)tval_result.tv_usec); 

Это возвращает Time elapsed: 1.000870 на моей машине.

 #include  clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000); 

Я всегда использую функцию clock_gettime (), возвращая время из часов CLOCK_MONOTONIC. Возвращаемое время – это количество времени, в секундах и наносекундах, с некоторой неопределенной точки в прошлом, например, запуск системы эпохи.

 #include  #include  #include  int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p) { return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) - ((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec); } int main(int argc, char **argv) { struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); // Some code I am interested in measuring clock_gettime(CLOCK_MONOTONIC, &end); uint64_t timeElapsed = timespecDiff(&end, &start); } 

Реализация портативного решения

Как уже упоминалось здесь, нет правильного решения ANSI с достаточной точностью для задачи измерения времени, я хочу написать о способах получения портативного и, по возможности, решения для измерения времени с высоким разрешением.

Монотонные часы против штампов времени

Вообще говоря, есть два способа измерения времени:

  • monoтонные часы;
  • текущая (дата) отметка времени.

Первый использует monoтонный счетчик часов (иногда он называется счетчиком тиков), который подсчитывает тики с предопределенной частотой, поэтому, если у вас есть значение галочки и частота известна, вы можете легко конвертировать тики в прошедшее время. На самом деле не гарантируется, что monoтонные часы каким-либо образом отражают текущее системное время, он также может подсчитывать тики с момента запуска системы. Но это гарантирует, что часы всегда запускаются все чаще, независимо от состояния системы. Обычно частота привязана к аппаратным источникам с высоким разрешением, поэтому она обеспечивает высокую точность (в зависимости от аппаратного обеспечения, но большинство современных аппаратных средств не имеют проблем с источниками синхронизации с высоким разрешением).

Второй способ предоставляет значение времени (даты), основанное на текущем значении системного тактового сигнала. Он также может иметь высокое разрешение, но у него есть один главный недостаток: на такое значение времени могут влиять различные системные настройки времени, такие как изменение часового пояса, переход на летнее время (DST), обновление сервера NTP, hibernate системы и т. Д. на. В некоторых случаях вы можете получить отрицательное истекшее значение времени, которое может привести к неопределенному поведению. На самом деле такой источник времени менее надежный, чем первый.

Поэтому первое правило в измерении временных интервалов – использовать monoтонные часы, если это возможно. Он обычно имеет высокую точность, и он надежен по дизайну.

Обратная страtagsя

При внедрении портативного решения стоит рассмотреть альтернативную страtagsю: использовать monoтонные часы, если они доступны, и отбрасывать временные метки, если в системе нет monoтонных часов.

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) имеет свои собственные единицы абсолютного времени Маха, которые представляют собой monoтонные часы. Лучший способ начать – статья Apple Technical Q & A QA1398: Единицы абсолютного времени Маха, которые описывают (с примерами кода), как использовать специфичный для Маха API API, чтобы получить monoтонные тики. Существует также местный вопрос об этом как альтернатива clock_gettime в Mac OS X, которая в конце может немного вас смутить, что делать с возможным переполнением значения, поскольку частота счетчика используется в виде числителя и знаменателя. Итак, краткий пример того, как получить прошедшее время:

  • получить числитель тактовой частоты и знаменатель:

     #include  #include  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 чтобы получить микросекунды. Вы можете найти тот же подход, который используется в Chromium’s time_mac.c . Если вам действительно нужна точность наносекунды, подумайте о том, как я могу использовать mach_absolute_time без переполнения? ,

Linux и UNIX

clock_gettime вызов – лучший способ для любой POSIX-совместимой системы. Он может запрашивать время от разных источников синхронизации, а тот, который нам нужен, – CLOCK_MONOTONIC . Не все системы с поддержкой CLOCK_MONOTONIC , поэтому первое, что вам нужно сделать, это проверить его доступность:

  • если _POSIX_MONOTONIC_CLOCK определяется значением >= 0 это означает, что CLOCK_MONOTONIC ;
  • если _POSIX_MONOTONIC_CLOCK определен как 0 это означает, что вы должны дополнительно проверить, работает ли он во время выполнения, я предлагаю использовать sysconf :

     #include  #ifdef _SC_MONOTONIC_CLOCK if (sysconf (_SC_MONOTONIC_CLOCK) > 0) { /* A monotonic clock presents */ } #endif 
  • в противном случае monoтонные часы не поддерживаются, и вы должны использовать резервную страtagsю (см. ниже).

Использование clock_gettime довольно прямолинейно:

  • получить значение времени:

     #include  #include  #include  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; 

Лучшей страtagsей gettimeofday является использование вызова gettimeofday : он не gettimeofday , но обеспечивает неплохое разрешение. Идея такая же, как и с clock_gettime , но для получения значения времени вы должны:

 #include  #include  #include  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 . Вместо этого он имеет свой собственный monoтонический источник синхронизации, определенный как CLOCK_SGI_CYCLE который вы должны использовать вместо CLOCK_MONOTONIC с clock_gettime .

Solaris и HP-UX

Solaris имеет свой собственный gethrtime таймера с высоким разрешением, который возвращает значение текущего таймера в наносекундах. Хотя более новые версии Solaris могут иметь clock_gettime , вы можете придерживаться gethrtime если вам нужно поддерживать старые версии Solaris.

Использование прост:

 #include  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  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  #include  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, которая реализует все описанные выше страtagsи (подробнее см. Ptimeprofiler * .c).

Наилучшая точность, которую вы можете получить, – это использование инструкции «rdtsc», основанной на x86, которая может обеспечивать разрешение на уровне часов (ne обязательно должен учитывать стоимость самого вызова rdtsc, который можно легко измерить запуск приложения).

Основной улов здесь – это измерение количества часов в секунду, которое не должно быть слишком жестким.

timespec_get от C11 возвращается к наносекундам, округляется до разрешения реализации.

Похоже на clock_gettime ANSI из POSIX ‘ clock_gettime .

Пример: printf выполняется каждые 100 мс на Ubuntu 15.10:

 #include  #include  #include  static long get_nanos(void) { struct timespec ts; timespec_get(&ts, TIME_UTC); return (long)ts.tv_sec * 1000000000L + ts.tv_nsec; } int main(void) { long nanos; long last_nanos; long start; nanos = get_nanos(); last_nanos = nanos; start = nanos; while (1) { nanos = get_nanos(); if (nanos - last_nanos > 100000000L) { printf("current nanos: %ld\n", nanos - start); last_nanos = nanos; } } return EXIT_SUCCESS; } 

Стандартный проект C11 N1570 7.27.2.5 Функция timespec_get говорит:

Если базой является TIME_UTC, член tv_sec устанавливается на количество секунд с момента реализации определенной эпохи, усеченный до целого значения, а член tv_nsec устанавливается на целое число наносекунд, округленное до разрешения системных часов. (321)

321). Хотя объект struct timespec описывает времена с разрешением наносекунды, доступное разрешение зависит от системы и может даже превышать 1 секунду.

C ++ 11 также получил std::chrono::high_resolution_clock : C ++ кросс-платформенный таймер с высоким разрешением

glibc 2.21 реализует его в sysdeps/posix/timespec_get.c как:

 int timespec_get (struct timespec *ts, int base) { switch (base) { case TIME_UTC: if (__clock_gettime (CLOCK_REALTIME, ts) < 0) return 0; break; default: return 0; } return base; } 

так ясно:

  • поддерживается только TIME_UTC

  • он пересылается в __clock_gettime (CLOCK_REALTIME, ts) , который представляет собой POSIX API: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html

    Linux x86-64 имеет системный вызов clock_gettime .

    Обратите внимание, что это не отказоустойчивый метод микро-бенчмаркинга, поскольку:

    • man clock_gettime говорит, что эта мера может иметь разрывы, если вы измените настройку системного времени во время работы вашей программы. Конечно, это должно быть редкое событие, и вы можете его игнорировать.

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

    По этим причинам getrusage() может быть лучшим лучшим инструментом для тестирования POSIX, несмотря на то, что максимальная точность в микросекундах ниже.

    Дополнительная информация: Время измерения в Linux - время против часов против getrusage против clock_gettime vs gettimeofday vs timespec_get?

Принятый ответ достаточно хорош. Но мое решение более просто. Я просто тестирую в Linux, использую gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0.

Alse use gettimeofday , tv_sec является частью второго, а tv_usecмикросекундами , а не миллисекундами .

 long currentTimeMillis() { struct timeval time; gettimeofday(&time, NULL); return time.tv_sec * 1000 + time.tv_usec / 1000; } int main() { printf("%ld\n", currentTimeMillis()); // wait 1 second sleep(1); printf("%ld\n", currentTimeMillis()); return 0; } 

Он печатает:

1522139691342 1522139692342 , ровно секунду.

Под windowsми:

 SYSTEMTIME t; GetLocalTime(&t); swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds); 
  • Настройка десятичной точности, .net
  • Давайте будем гением компьютера.