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

StackOverflow https://stackoverflow.com/questions/361363

  •  21-08-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

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

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

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

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

Поэтому первое правило измерения временных интервалов — по возможности использовать монотонные часы.Обычно он имеет высокую точность и надежен по своей конструкции.

Запасная стратегия

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

Окна

Есть замечательная статья под названием Получение меток времени с высоким разрешением на MSDN об измерении времени в Windows, где описаны все подробности, которые вам могут понадобиться о поддержке программного и аппаратного обеспечения.Чтобы получить высокоточную отметку времени в Windows, вам необходимо:

  • запросить частоту таймера (тактов в секунду) с помощью Частота выполнения запроса:

    LARGE_INTEGER tcounter;
    LARGE_INTEGER freq;    
    
    if (QueryPerformanceFrequency (&tcounter) != 0)
        freq = tcounter.QuadPart;
    

    Частота таймера фиксируется при загрузке системы, поэтому вам нужно получить ее только один раз.

  • запросить текущее значение тиков с помощью ЗапросPerformanceCounter:

    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 и выше.

ОС Х (macOS)

OS X (macOS) имеет свои собственные единицы абсолютного времени в Махах, которые представляют собой монотонные часы.Лучший способ начать — это статья Apple. Технические вопросы и ответы 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 чтобы получить микросекунды.Вы можете найти тот же подход, который используется в Chromium. time_mac.c.Если вам действительно нужна наносекундная точность, прочтите Как я могу использовать mach_absolute_time без переполнения?.

Линукс и 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;
}

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

СГИ ИРИКС

ИРИКС имеет 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.

БеОС

БеОС также имеет собственный интерфейс таймера высокого разрешения. 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;
}

ОС/2

ОС/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);
    

Пример реализации

Вы можете взглянуть на плибсис библиотека, реализующая все описанные выше стратегии (подробнее см. ptimeprofiler*.c).

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 IS TIME_UTC, член TV_SEC устанавливается на количество секунд, так как реализация определяла эпоха, усеченная до всего значения, а член TV_NSEC устанавливается на неотъемлемое количество наносекунд, окруженное разрешением системных часов.(321)

321) Хотя объект структуры 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 против clock_gettime против gettimeofday против timespec_get?

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

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

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

Под окнами:

SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top