仅使用ANSI C,有没有办法以毫秒或更高的精度来测量时间?我正在浏览time.h,但只找到了第二个精度函数。
仅使用ANSI C,有没有办法以毫秒或更高的精度来测量时间?我正在浏览time.h,但只找到了第二个精度函数。
Answers:
没有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
在我的机器上返回。
timeval::tv_usec
总是在不到一秒钟的时间里,这是循环的。即,为了采用大于1秒的时间差,您应该:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
timersub
函数内部。我们可以tval_result
按原样使用值(tv_sec和tv_usec)。
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
CLOCKS_PER_SEC / 1000
可能不精确,这可能会影响最终结果(尽管根据我的经验,CLOCKS_PER_SEC
它始终是1000的倍数)。这样做(1000 * clock()) / CLOCKS_PER_SEC
不易受到除法不精确性的影响,但另一方面更容易发生溢出。只是一些要考虑的问题。
我总是使用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);
}
clock_gettime(CLOCK_MONOTONIC, ...)
,甚至还有功能测试宏_POSIX_MONOTONIC_CLOCK
。
实施便携式解决方案
正如这里已经提到的那样,没有合适的ANSI解决方案来解决时间测量问题,因此我想写一些有关如何获得便携式和高分辨率时间测量解决方案的方法。
单调时钟与时间戳
一般来说,有两种时间测量方法:
第一个使用单调时钟计数器(有时称为滴答计数器),它以预定义的频率对滴答进行计数,因此,如果您具有滴答值并且知道频率,则可以轻松地将滴答转换为经过的时间。实际上,不能保证单调时钟以任何方式反映当前系统时间,它也可能在系统启动后计数滴答声。但是,它保证了无论系统状态如何,时钟总是以递增的方式运行。通常,频率与硬件高分辨率源有关,这就是为什么它提供高精度的原因(取决于硬件,但是大多数现代硬件在高分辨率时钟源上都没有问题)。
第二种方法基于当前系统时钟值提供(日期)时间值。它可能也具有高分辨率,但是它有一个主要缺点:这种时间值可能会受到不同系统时间调整的影响,例如,时区更改,夏令时(DST)更改,NTP服务器更新,系统休眠等上。在某些情况下,您会获得一个负的经过时间值,这可能导致未定义的行为。实际上,这种时间源不如第一种时间源可靠。
因此,时间间隔测量的第一条规则是尽可能使用单调时钟。它通常具有很高的精度,并且设计可靠。
后备策略
在实现便携式解决方案时,值得考虑一个后备策略:如果可用,则使用单调时钟;如果系统中没有单调时钟,则使用后备时间戳方法。
视窗
在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
,但从Windows Vista及更高版本开始提供。OS X(macOS)
OS X(macOS)有自己的马赫绝对时间单位,代表单调时钟。最好的开始方法是Apple的文章《技术问答QA1398:马赫绝对时间单位》,该文章(与代码示例一起)描述了如何使用特定于马赫的API来获取单调刻度。在Mac OS X中还有一个关于它的本地问题,称为clock_gettime替代,最后,您可能会有点困惑可能的值溢出,因为计数器频率以分子和分母的形式使用。因此,一个简短的示例如何获得经过时间:
得到时钟频率分子和分母:
#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而不溢出?。
Linux和UNIX
clock_gettime
在任何适合POSIX的系统上,致电都是您最好的方式。它可以查询来自不同时钟源的时间,而我们需要的是CLOCK_MONOTONIC
。并非所有具有clock_gettime
支持功能的系统CLOCK_MONOTONIC
,因此您需要做的第一件事就是检查其可用性:
_POSIX_MONOTONIC_CLOCK
定义为一个值,>= 0
则表示该CLOCK_MONOTONIC
值可用;如果_POSIX_MONOTONIC_CLOCK
定义了if ,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
with 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
您应该以与Solaris相同的方式使用。
操作系统
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)。
timespec_get
不是单调的。
timespec_get
从C11
返回最多十亿分之一秒,四舍五入到实现的分辨率。
看起来像是从POSIX'提取的ANSI clock_gettime
。
示例:printf
在Ubuntu 15.10上每100毫秒执行一次:
#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)尽管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时钟vs getrusage vs clock_gettime vs gettimeofday vs timespec_get?
您可能获得的最佳精度是使用仅x86的“ rdtsc”指令,该指令可以提供时钟级别的分辨率(当然,必须考虑到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
,恰好是一秒钟。
在Windows下:
SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);