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


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

8 109

8 ответов:

нет функции ANSI C, которая обеспечивает разрешение лучше, чем 1 второй раз, но функция POSIX gettimeofday обеспечивает с точностью до микросекунд. Функция clock измеряет только время, затраченное на выполнение процесса, и не является точной во многих системах.

вы можете использовать эту функцию, как это:

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 на моей машине.

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

Я всегда использую функцию clock_gettime (), возвращая время из CLOCK_MONOTONIC clock. Возвращаемое время - это количество времени, в секундах и наносекундах, начиная с некоторого неопределенного момента в прошлом, такого как запуск системы эпохи.

#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);
}

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

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

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

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

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

второй способ обеспечивает (дата)значение времени на основе текущего значения системных часов. Он также может иметь высокое разрешение, но есть один недостаток: этот вид значения времени может быть повлиян на различными регулировками системного времени, т. е. изменением часового пояса, изменением летнего времени (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 технические Q & A QA1398: абсолютное время Маха Единицы который описывает (с примерами кода), как использовать Mach-specific 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. Если вам действительно нужна наносекундная точность считывания как я могу использовать mach_absolute_time без переполнения?.

Linux и UNIX

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

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 для подробная информация.)

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

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

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

выглядит как ANSI ripoff из 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;
}

The С11 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 - время против против против против часовой вызов getrusage системах gettimeofday против timespec_get?

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

Alse use 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 ровно секунду.

под windows:

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