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

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

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

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

как уже упоминалось здесь, что нет надлежащего решения 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 технический Q&A 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 чтобы получить микросекунды. Вы можете найти тот же подход, используемый в time_mac.c. Если вам действительно нужна наносекундная точность, подумайте о чтении как я могу использовать mach_absolute_time без переполнения?.

Linux и UNIX

на clock_gettime call - ваш лучший способ в любой системе 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 "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;
}

на С11 N1570 типовой проект 7.27.2.5 функция timespec_get говорит:

если base-TIME_UTC, члену tv_sec присваивается номер секунд с реализация определена epoch, усечена до целого значения, а член tv_nsec задайте интегральное число наносекунд, округленное до разрешения системных часов. (321)

321) хотя объект struct timespec описывает времена с разрешением наносекунды, доступный разрешение зависит от системы и может быть даже больше, чем 1 секунда.

в C++11 также получил std::chrono::high_resolution_clock: Кросс-Платформенное Высокое Разрешение C++ Таймер

glibc 2.21 реализует под sysdeps/posix/timespec_get.c as:

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-time vs clock vs getrusage vs clock_gettime vs gettimeofday vs timespec_get?


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

Alse использовать gettimeofday на tv_sec часть вторая, и tv_usec is микросекунд, а не МС.

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