Как измерить время в миллисекундах с помощью ANSI C?
используя только ANSI C, есть ли способ измерить время с точностью до миллисекунд или больше? Я просматривал время.h но я нашел только вторые функции точности.
8 ответов:
нет функции ANSI C, которая обеспечивает разрешение лучше, чем 1 второй раз, но функция POSIX
gettimeofday
обеспечивает с точностью до микросекунд. Функция clock измеряет только время, затраченное на выполнение процесса, и не является точной во многих системах.вы можете использовать эту функцию, как это:
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
на моей машине.
Я всегда использую функцию clock_gettime (), возвращая время из CLOCK_MONOTONIC clock. Возвращаемое время - это количество времени, в секундах и наносекундах, начиная с некоторого неопределенного момента в прошлом, такого как запуск системы эпохи.
#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); }
реализация портативного решения
монотонные часы против штампов времени
вообще говоря, есть два способа времени измерение:
- монотонные часы;
- текущая (дата)отметка времени.
первый использует монотонный счетчик часов (иногда его называют счетчиком тиков), который подсчитывает тики с предопределенной частотой, поэтому, если у вас есть значение ТИКов и частота известна, вы можете легко конвертировать Тики в прошедшее время. Фактически не гарантируется, что монотонные часы каким-либо образом отражают текущее системное время, он также может считать Тики, Так как a запуск системы. Но это гарантирует, что часы всегда работают в возрастающем режиме независимо от состояния системы. Обычно частота привязана к аппаратному источнику высокого разрешения, поэтому она обеспечивает высокую точность (зависит от аппаратного обеспечения, но большинство современных аппаратных средств не имеет проблем с источниками синхронизации высокого разрешения).
второй способ обеспечивает (дата)значение времени на основе текущего значения системных часов. Он также может иметь высокое разрешение, но есть один недостаток: этот вид значения времени может быть повлиян на различными регулировками системного времени, т. е. изменением часового пояса, изменением летнего времени (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 технические Q & A QA1398: абсолютное время Маха Единицы который описывает (с примерами кода), как использовать Mach-specific 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. Если вам действительно нужна наносекундная точность считывания как я могу использовать mach_absolute_time без переполнения?.Linux и UNIX
The
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
который вы должны использовать так же, как и на Солярис.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 для подробная информация.)
лучшая точность, которую вы можете получить,-это использование инструкции x86-only "rdtsc", которая может обеспечить разрешение на уровне часов (ne, конечно, должен учитывать стоимость самого вызова rdtsc, который можно легко измерить при запуске приложения).
основной улов здесь измеряет количество часов в секунду, что не должно быть слишком сложно.
timespec_get
от C11 возвращает значение до наносекунд, округленное до разрешения реализации.выглядит как ANSI ripoff из 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; }
The С11 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.htmlLinux x86-64 имеет
clock_gettime
системный вызов.обратите внимание, что это не отказоустойчивый метод микро-бенчмаркинга, потому что:
man clock_gettime
говорит, что эта мера может иметь разрывы, если вы измените некоторые настройки системного времени во время выполнения программы. Это должно быть редкое событие, конечно, и вы могли бы игнорировать его.это измеряет время стены, поэтому, если планировщик решит забыть о вашей задаче, он будет работать дольше.
для тех, причины
getrusage()
может быть лучше лучше инструмент бенчмаркинга POSIX, несмотря на то, что это более низкая максимальная точность микросекунды.дополнительная информация: измерения времени в Linux - время против против против против часовой вызов getrusage системах gettimeofday против 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
ровно секунду.