仅使用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
在我的机器上返回.
#includeclock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
我总是使用clock_gettime()函数,从CLOCK_MONOTONIC时钟返回时间.返回的时间是从过去的一些未指定的点开始的时间量,以秒和纳秒为单位,例如系统启动时期.
#include#include #include 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上获得高精度时间戳,您应该:
使用QueryPerformanceFrequency查询计时器频率(每秒滴答数):
LARGE_INTEGER tcounter; LARGE_INTEGER freq; if (QueryPerformanceFrequency (&tcounter) != 0) freq = tcounter.QuadPart;
定时器频率在系统启动时是固定的,因此您只需要获取一次.
使用QueryPerformanceCounter查询当前的ticks值:
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上使用两种后备解决方案:
GetTickCount提供自系统启动以来经过的毫秒数.它每49.7天包裹一次,所以在测量更长的间隔时要小心.
GetTickCount64是64位版本GetTickCount
,但从Windows Vista及更高版本开始提供.
OS X(macOS)
OS X(macOS)有自己的Mach绝对时间单位,表示单调时钟.最好的方法是Apple的文章技术问答QA1398:Mach绝对时间单位,它描述了(使用代码示例)如何使用特定于Mach的API来获得单调滴答.在Mac OS X中还有一个关于它的本地问题叫做clock_gettime替代方案,最后可能会让你有点困惑如何处理可能的值溢出,因为计数器频率以分子和分母的形式使用.那么,一个简短的例子如何获得经过的时间:
得到时钟频率分子和分母:
#include#include 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#ifdef _SC_MONOTONIC_CLOCK if (sysconf (_SC_MONOTONIC_CLOCK) > 0) { /* A monotonic clock presents */ } #endif
否则不支持单调时钟,您应该使用后备策略(见下文).
使用clock_gettime
非常简单:
得到时间价值:
#include#include #include 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#include #include 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版本,则可以坚持使用.
用法很简单:
#includevoid 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
,它返回自计算机启动以来经过的微秒数.
用法示例:
#includevoid 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#include ULONG freq; DosTmrQueryFreq (&freq);
查询当前的ticks值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).
timespec_get
来自C11
返回最多纳秒,舍入到实现的分辨率.
看起来像POSIX'的ANSI ripoff clock_gettime
.
示例:printf
在Ubuntu 15.10上每100毫秒完成一次:
#include#include #include 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的getrusage VS clock_gettime VS gettimeofday的VS timespec_get?