Как измерить время в миллисекундах с помощью ANSI C? - PullRequest
116 голосов
/ 12 декабря 2008

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

Ответы [ 8 ]

85 голосов
/ 12 декабря 2008

Нет функции 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.

44 голосов
/ 15 декабря 2008
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
26 голосов
/ 19 декабря 2008

Я всегда использую функцию 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);
}
17 голосов
/ 20 июня 2016

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

Поскольку здесь уже упоминалось, что не существует надлежащего решения 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) имеет собственные единицы абсолютного времени Маха, которые представляют собой монотонные часы. Лучший способ начать - статья Apple Технические вопросы и ответы QA1398: Единицы абсолютного времени Маха , в которой описывается (с примерами кода), как использовать 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 Chromium. Если вам действительно нужна точность в наносекунды, подумайте о прочтении Как я могу использовать 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 для получения подробной информации).

6 голосов

timespec_get от C11

Возвращает до наносекунд, округленных до разрешения реализации.

Похоже на разбор ANSI от POSIX 'clock_gettime.

Пример: 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), который является API POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html

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

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

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

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

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

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

4 голосов
/ 12 декабря 2008

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

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

1 голос
/ 27 марта 2018

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

Также используйте 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, ровно секунда.

0 голосов
/ 11 сентября 2013

Под окнами:

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