Як виміряти час у мілісекундах за допомогою ANSI C?


127

Використовуючи лише ANSI C, чи є можливість виміряти час з точністю до мілісекунд або більше? Я переглядав time.h, але знайшов лише функції другої точності.


4
Зверніть увагу на різницю між точністю та точністю. Ви можете отримати час з мілісекундною точністю , взявши час в секундах і помноживши на 1000, але це не має сенсу. Функції точності мс не обов'язково мають точність мс, хоча вони, як правило, краще, ніж точність 1 с.
Стів Джессоп

2
Проста відповідь "НІ", ANSI C не підтримує мілісекундну точність або краще. Більш складна відповідь залежить від того, що ви намагаєтеся зробити - відверто кажучи, вся область - це кошмар - навіть якщо ви дозволяєте використовувати широко доступні функції Posix. Ви використовуєте термін "міра", тому я припускаю, що вас цікавить інтервал, а не час "настінний годинник". Але ви намагаєтеся виміряти абсолютний період часу або використання процесора вашим процесом?
Штрих

2
Просто хотів сказати SOF, щойно врятував моє бекон, знову ж таки ;-)
corlettk

Відповіді:


92

Не існує функції 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на моїй машині.


30
Незначне застереження: gettimeofday () не є монотонним, тобто може стрибати (і навіть йти назад), якщо, наприклад, ваша машина намагається зберегти синхронізацію з мережевим сервером часу або іншим джерелом часу.
Штрих

3
Якщо бути точним: в ISO C99 (який, на мою думку, сумісний у цій частині з ANSI C), немає навіть гарантії будь-якої часової роздільної здатності. (ISO C99, 7.23.1p4)
Roland Illig

6
Варто зазначити, що timeval::tv_usecце завжди менше однієї секунди, це петлі. Тобто для того, щоб зайняти різницю часу більше 1 секунди, вам слід:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
Олександр Малахов

4
@Dipstick: Але зауважте, що, наприклад, NTP ніколи не переміщує ваш годинник назад, поки ви прямо не скажете йому це зробити.
thejh

1
@AlexanderMalakhov логіка віднімання часу є інкапсульованою внутрішньою timersubфункцією. Ми можемо використовувати tval_resultзначення (tv_sec і tv_usec) як є.
x4444

48
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);

У багатьох системах CLOCKS_PER_SEC встановлено 1000000. Роздрукуйте його значення, щоб бути впевненим, перш ніж використовувати його таким чином.
ysap

16
Оскільки тактова частота в секунду не має значення, яке це значення, отримане значення тактового () / CLOCKS_PER_SEC буде за секунди (принаймні, так повинно бути). Ділення на 1000 перетворює на мілісекунди.
Девід Янг

14
Відповідно до Довідкового посібника C, значення__ годин можуть змінюватися, починаючи приблизно з 36 хвилин. Якщо ви вимірюєте довгі обчислення, вам потрібно знати про це.
CyberSkull

4
Також майте на увазі, що ціле ділення CLOCKS_PER_SEC / 1000може бути неточним, що може вплинути на кінцевий результат (хоча, на мій досвід CLOCKS_PER_SEC, завжди було кратним 1000). Робота (1000 * clock()) / CLOCKS_PER_SECменш схильна до неточності поділу, але, з іншого боку, більш схильна до переповнення. Просто деякі питання, які слід врахувати.
Cornstalks

4
Це не вимірює процесорний час, а не стіновий час?
krs013

28

Я завжди використовую функцію 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);
}

5
clock_gettime () - це не ANSI C.
PowerApp101,

1
Також CLOCK_MONOTONIC не реалізований у багатьох системах (у тому числі на багатьох платформах Linux).
Штрих

2
@ PowerApp101 Немає хорошого / надійного способу ANSI C для цього. Багато хто з інших відповідей покладаються на POSIX, а не на ANCI C. Що говорити, я вважаю, що сьогодні. @Dipstick Сьогодні я вважаю, що більшість сучасних платформ [потрібна цитата] підтримують, clock_gettime(CLOCK_MONOTONIC, ...)і є навіть макрос тестової функції _POSIX_MONOTONIC_CLOCK.
omninonsense

22

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

Оскільки тут вже було сказано, що не існує належного рішення 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) має власні абсолютні часові одиниці 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).


«Немає належного вирішення ANSI з достатнім ступенем точності для задачі вимірювання часу»: є C11 timespec_get: stackoverflow.com/a/36095407/895245
Чіро Сантіллі郝海东冠状病六四事件法轮功

1
Це все ще неправильний спосіб вимірювання часу виконання коду. timespec_getне є монотонним.
Олександр Саприкін

11

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?


це правильна відповідь станом на 2017 рік, навіть MSVC має цю функцію; з точки зору бенчмаркінгу шукайте те, що читає чіп-регістр (новіші версії процесорів x86 з розширеннями PT та відповідні новіші версії ядра /

4

Найкраща точність, яку ви можете отримати, - це використання інструкції "rdtsc" тільки для x86, яка може забезпечити дозвіл на рівні годинника (звичайно, слід враховувати вартість самого виклику rdtsc, який легко виміряти на запуск програми).

Основна уловка тут - вимірювання кількості годин в секунду, що не повинно бути занадто важким.


3
Можливо, вам також потрібно буде потурбуватися про спорідненість процесора, оскільки на деяких машинах ви можете надсилати дзвінки RDTSC на більше ніж один процесор, і їх лічильники RDTSC можуть не синхронізуватися.
Буде Дін

1
І крім того, деякі процесори не мають монотонно зростаючого TSC - продумайте режими економії енергії, які знижують частоту процесора. Використання RDTSC для чого завгодно, але дуже короткого локалізованого таймінгу - ДУЖЕ погана ідея.
snemarch

До речі, основний дрейф, згаданий @WillDean та використання rdtsc для синхронізації, є причиною, коли ряд ігор не працював на (ранніх?) Багатоядерних процесорах AMD64 - мені довелося обмежитися одноядерним спорідненістю на моїй x2 4400+ для ряд назв.
снімарх

2

Прийнята відповідь досить хороша. Але моє рішення простіше. Я просто тестую в 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, рівно секунду.


-4

Під вікнами:

SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);

1
це ansi C згідно запиту?
Gyom
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.