Используя только ANSI C, есть ли способ измерить время с точностью до миллисекунд или более? Я просматривал time.h, но нашел только функции второй точности.
Используя только 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
на мою машину.
timeval::tv_usec
всегда меньше одной секунды, это зацикливание. Т.е. для того, чтобы отсчитывать разницу во времени больше 1 секунды, вам необходимо:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
timersub
функции. Мы можем использовать tval_result
значения (tv_sec и tv_usec) как есть.
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
CLOCKS_PER_SEC / 1000
может быть неточным, что может повлиять на конечный результат (хотя, по моему опыту, CLOCKS_PER_SEC
он всегда был кратен 1000). Выполнение (1000 * clock()) / CLOCKS_PER_SEC
менее восприимчиво к делению неточностям, но с другой стороны, более восприимчив к переполнению. Просто некоторые вопросы для рассмотрения.
Я всегда использую функцию clock_gettime (), возвращающую время из часов CLOCK_MONOTONIC. Возвращаемое время - это количество времени в секундах и наносекундах, прошедшее с некоторой неопределенной точки в прошлом, такой как запуск системы эпохи.
#include <stdio.h>
#include <stdint.h>
#include <time.h>
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);
}
clock_gettime(CLOCK_MONOTONIC, ...)
и есть даже макрос проверки функций _POSIX_MONOTONIC_CLOCK
.
Внедрение портативного решения
Поскольку здесь уже упоминалось, что не существует подходящего решения 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
, но она доступна начиная с Windows Vista и выше.OS X (macOS)
OS X (macOS) имеет собственные единицы абсолютного времени Маха, которые представляют собой монотонные часы. Лучший способ начать - это статья Apple Technical Q&A QA1398: Absolute Time Units, в которой описывается (с примерами кода), как использовать API, специфичный для Mach, для получения монотонных тиков. Существует также местный вопрос об этом, называемый альтернативой 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
чтобы получить микросекунды. Вы можете найти тот же подход, что и в chromium time_mac.c . Если вам действительно нужна точность до наносекунды, подумайте о прочтении Как я могу использовать 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).
timespec_get
: stackoverflow.com/a/36095407/895245
timespec_get
не монотонный.
timespec_get
из C11
Возвращает значение с точностью до наносекунд, округленное до разрешения реализации.
Похоже на грабеж ANSI из POSIX ' clock_gettime
.
Пример: a printf
выполняется каждые 100 мс в Ubuntu 15.10:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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 говорит»:
Если base равно 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 - время vs часы vs getrusage vs clock_gettime vs gettimeofday vs timespec_get?
Наилучшая точность, которую вы можете получить, - это использование инструкции "rdtsc" только для x86, которая может обеспечить разрешение на уровне часов (конечно же, при этом необходимо учитывать стоимость самого вызова rdtsc, которую можно легко измерить на запуск приложения).
Основной уловкой здесь является измерение количества тактов в секунду, что не должно быть слишком сложно.
Принятый ответ достаточно хорош, но мое решение более простое. Я просто тестирую в Linux, использую gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0.
Alse использование 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
, ровно секунду.