如何用毫秒测量时间使用ANSI C?

只使用ANSI C,有没有什么方法可以用毫秒精度或更高来度量时间? 我正在浏览time.h,但我只发现了第二个精度函数。

没有提供优于1秒时间分辨率的ANSI C函数,但POSIX函数gettimeofday提供了微秒的分辨率。 时钟function仅测量进程花费的时间量,在许多系统上不准确。

你可以像这样使用这个函数:

 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在我的机器上返回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); } 

实施便携式解决scheme

正如前面已经提到的那样,在时间测量问题上没有足够精确的ANSI解决scheme,我想写一些关于如何获得便携式(如果可能)的高分辨率时间测量解决scheme的方法。

单调时钟与时间戳

一般来说有两种时间测量方式:

  • 单调时钟;
  • 当前(date)时间戳。

第一个使用单调时钟计数器(有时称为计时器计数器),它以预定义的频率计数刻度,所以如果您有一个刻度值并且频率已知,则可以轻松地将刻度转换为经过的时间。 实际上不能保证单调的时钟以任何方式反映当前的系统时间,它也可以从系统启动以来计数滴答。 但是它保证了时钟总是以不断增加的方式运行,而不pipe系统状态如何。 通常频率被绑定到一个硬件高分辨率的源,这就是为什么它提供了一个高精度(取决于硬件,但大多数现代硬件没有高分辨率时钟源的问题)。

第二种方式根据当前系统时钟值提供(date)时间值。 它也可能具有很高的分辨率,但是它有一个主要的缺点:这种时间值可能受到不同系统时间调整的影响,即时区变化,夏令时(DST)变化,NTP服务器更新,系统hibernate等等上。 在某些情况下,您可能会得到负面的经过时间值,这可能导致未定义的行为。 实际上这种时间源比第一个时间源不可靠。

所以时间间隔测量的第一条规则是尽可能使用单调时钟。 它通常精度高,devise可靠。

后备战略

在实施便携式解决scheme时,值得考虑一种回退策略:如果可用,使用单调时钟,如果系统中没有单调时钟,则回退到时间戳。

视窗

有一篇很棒的文章,叫做“ 获取 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); 

根据微软的说法,在大多数情况下,在Windows XP和更高版本上,这种方法不应该有任何问题。 但是,您也可以在Windows上使用两个备用解决scheme:

  • GetTickCount提供自系统启动以来经过的毫秒数。 它每49.7天包裹一次,因此要小心测量更长的间隔。
  • GetTickCount64是GetTickCount的64位版本,但可从Windows Vista及更高版本开始使用。

OS X(macOS)

OS X(macOS)有它自己的马赫绝对时间单位,代表一个单调时钟。 最好的方法是苹果的文章技术问答QA1398:马赫绝对时间单位 ,描述(与代码示例)如何使用特定马赫的API来获得单调滴答。 在Mac OS X中还有一个关于它的地方性问题,叫做clock_gettime alternative,最后可能会让你感到困惑,因为计数器频率是以分子和分母的forms使用的,所以可能会出现数值溢出。 所以,一个简单的例子如何获得stream逝的时间:

  • 得到时钟频率的分子和分母:

     #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中find相同的方法。 如果您真的需要纳秒级精度,请考虑阅读如何使用mach_absolute_time而不溢出? 。

Linux和UNIX

clock_gettime调用是任何POSIX友好系统上的最佳方式。 它可以从不同的时钟源查询时间,我们需要的是CLOCK_MONOTONIC 。 并非所有的clock_gettime支持CLOCK_MONOTONIC ,所以你需要做的第一件事是检查它的可用性:

  • 如果_POSIX_MONOTONIC_CLOCK被定义为值>= 0则意味着CLOCK_MONOTONIC可用;
  • 如果_POSIX_MONOTONIC_CLOCK被定义为0这意味着你应该另外检查它是否在运行时工作,我build议使用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调用:它不是一个单调的,但它提供了一个很好的解决scheme。 这个想法和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_gettime来代替CLOCK_MONOTONIC

Solaris和HP-UX

Solaris有自己的高分辨率定时器接口gethrtime ,它返回当前定时器的值,以毫微秒为单位。 虽然较新版本的Solaris可能有clock_gettimegethrtime如果您需要支持旧的Solaris版本,则可以坚持使用gethrtime

用法很简单:

 #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缺lessclock_gettime ,但是它支持您应该像在Solaris上一样使用的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查询当前的ticks值:

     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”指令,它可以提供时钟级别的分辨率(当然,必须考虑到rdtsc调用本身的成本,这可以很容易地测量应用程序启动)。

这里的主要问题是测量每秒的时钟数量,这不应该太难。

来自C11的timespec_get返回的时间高达纳秒,取决于实现的分辨率。

看起来像POSIX'clock_gettime的ANSI ripoff。

例如:在Ubuntu 15.10上,每100毫秒执行一次printf

 #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)尽pipe一个struct timespec对象用纳秒分辨率描述时间,但是可用的分辨率是依赖于系统的,甚至可能大于1秒。

C ++ 11也得到了std::chrono::high_resolution_clock : C ++跨平台高分辨率定时器

glibc 2.21sysdeps/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 : __clock_gettime (CLOCK_REALTIME, ts)

    Linux x86-64有一个clock_gettime系统调用。

    请注意,这不是一个防故障的微基准testing方法,因为:

    • man clock_gettime说,如果在程序运行时更改某些系统时间设置,则此度量可能会有不连续性。 当然这应该是一个罕见的事件,你可能会忽略它。

    • 这是衡量墙的时间,所以如果调度员决定忘记你的任务,它会显得运行更长时间。

    由于这些原因, getrusage()可能是一个更好的POSIX基准testing工具,尽pipe它的最大精度是微秒级。

    更多的信息在: 在Linux下测量时间 – 时间vs时钟vs getrusage vs clock_gettime vs gettimeofday vs timespec_get?

在windows下:

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