Как измерить время в миллисекундах с использованием ANSI C?

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

Ответы

Ответ 1

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

Поскольку здесь уже упоминалось, что нет подходящего решения ANSI с достаточной точностью для задачи измерения времени, я хочу написать о том, как получить портативное и, если возможно, решение для измерения времени с высоким разрешением.

Монотонные часы против отметок времени

Вообще говоря, есть два способа измерения времени:

  • монотонные часы;
  • текущая (дата) timestamp.

Первый использует монотонный счетчик часов (иногда он называется счетчиком тиков), который подсчитывает тики с предопределенной частотой, поэтому, если у вас есть значение тиков и частота известна, вы можете легко преобразовать тики в истекшее время. Фактически не гарантируется, что монотонные часы каким-либо образом отражают текущее системное время, они также могут считать тики с момента запуска системы. Но это гарантирует, что часы всегда будут увеличиваться независимо от состояния системы. Обычно частота связана с аппаратным источником высокого разрешения, поэтому она обеспечивает высокую точность (зависит от аппаратного обеспечения, но у большинства современного оборудования нет проблем с источниками тактового сигнала с высоким разрешением).

Второй способ предоставляет значение времени (даты), основанное на текущем значении системных часов. Он также может иметь высокое разрешение, но у него есть один существенный недостаток: на этот тип времени могут влиять различные настройки системного времени, например, изменение часового пояса, изменение летнего времени (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 чтобы получить микросекунды. Вы можете найти тот же подход, который используется в 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.

Солярис и 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).

Ответ 5

timespec_get из C11

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

Похоже на clock_gettime 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?

Ответ 6

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

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

Ответ 7

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

Ответ 8

В окнах:

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