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


127

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


4
Обратите внимание на разницу между точностью и аккуратностью. Вы можете получить время с точностью до миллисекунды , взяв время в секундах и умножив на 1000, но это бесполезно. Функции точности мс не обязательно имеют точность мс - хотя обычно они дают точность лучше, чем 1 с.
Стив Джессоп,

2
Простой ответ - НЕТ, ANSI C не поддерживает точность в миллисекундах или лучше. Более сложный ответ зависит от того, что вы пытаетесь сделать - откровенно говоря, вся область - это кошмар - даже если вы разрешите использовать широко доступные функции Posix. Вы используете термин «мера», поэтому я предполагаю, что вас интересует интервал, а не время «настенных часов». Но пытаетесь ли вы измерить абсолютный период времени или использование процессора вашим процессом?
Dipstick

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

3
Чтобы быть точным: в ISO C99 (который, я думаю, в этой части совместим с ANSI C) нет даже гарантии какого- либо разрешения по времени. (ISO C99, 7.23.1p4)
Роланд Иллиг

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
Поскольку это тактовая частота в секунду, не имеет значения, какое это значение, результирующее значение от clock () / CLOCKS_PER_SEC будет в секундах (по крайней мере, должно быть). Деление на 1000 превращает это в миллисекунды.
Дэвид Янг

14
Согласно справочному руководству по C, значения clock_t могут быть циклическими, начиная примерно с 36 минут. Если вы измеряете длительные вычисления, вам нужно об этом знать.
CyberSkull

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

4
Разве это не измеряет время процессора, а не время стены?
krs013 01

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).
Dipstick

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) имеет собственные единицы абсолютного времени Маха, которые представляют собой монотонные часы. Лучший способ начать - это статья Apple Technical Q&A QA1398: Absolute Time Units, в которой описывается (с примерами кода), как использовать 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 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;
}

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).


«не существует подходящего решения ANSI с достаточной точностью для проблемы измерения времени»: есть C11 timespec_get: stackoverflow.com/a/36095407/895245
Чиро Сантилли 郝海东 冠状 病 六四 事件

1
Это все еще неправильный способ измерения времени выполнения кода. timespec_getне монотонный.
Александр Сапрыкин

11

timespec_get из C11

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

Похоже на грабеж 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 говорит»:

Если 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.html

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

    Обратите внимание, что это не надежный метод микротестирования, потому что:

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

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

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

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


это правильный ответ по состоянию на 2017 год, даже MSVC имеет эту функцию; Что касается сравнительного анализа, найдите что-нибудь, что читает регистр чипа (более новые версии процессоров x86 с расширениями PT и соответствующие более новые версии ядра Linux / perf)

4

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

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


3
Возможно, вам также придется позаботиться о сродстве процессора, потому что на некоторых машинах вы можете отправлять вызовы RDTSC более чем одному процессору, и их счетчики RDTSC могут не синхронизироваться.
Уилл Дин

1
Более того, у некоторых процессоров нет монотонно увеличивающегося TSC - подумайте о режимах энергосбережения, которые снижают частоту процессора. Использование RDTSC для чего угодно, кроме очень коротких локализованных таймингов - ОЧЕНЬ плохая идея.
snemarch

Между прочим, дрейф ядра, упомянутый @WillDean, и использование rdtsc для измерения времени является причиной того, что ряд игр не работал (на ранних этапах?) Многоядерных процессоров AMD64 - мне пришлось ограничить одноядерную привязку на моем x2 4400+ для ряд названий.
snemarch

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.