Використовуючи лише 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) має власні абсолютні часові одиниці Mach, які представляють монотонні годинники. Найкращим способом для початку є стаття Apple Технічні запитання та запитання QA1398: Машини абсолютного часу Маха, де описано (з прикладами коду), як використовувати 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's 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;
}
ОС / 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
від С11
Повертається до наносекунд, округлених до роздільної здатності реалізації.
Схоже на 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 говорить»:
Якщо базовою є TIME_UTC, член tv_sec встановлюється на кількість секунд з моменту епохи, визначеної реалізацією, усікається на ціле значення, а член tv_nsec встановлюється на інтегральне число наносекунд, округлене до роздільної здатності системного тактового часу. (321)
321) Хоча структурний часовий об'єкт описує часи з наносекундною роздільною здатністю, наявна роздільна здатність залежить від системи і може навіть перевищувати 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 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
, рівно секунду.