仅使用 ANSI C,有没有办法以毫秒或更高的精度测量时间?我正在浏览 time.h 但我只找到了第二个精度函数。
8 回答
没有提供优于 1 秒时间分辨率的 ANSI C 函数,但 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 上有一篇很棒的文章,名为Acquiring high-resolution time stamps,介绍了 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 的文章Technical Q&A QA1398: Mach Absolute Time Units,它描述了(带有代码示例)如何使用特定于 Mach 的 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
定义了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
调用:它不是单调的,但它提供了相当好的分辨率。这个想法与 with 相同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 可能有,但如果您需要支持旧的 Solaris 版本clock_gettime
,您可以坚持使用。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 缺少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;
}
操作系统/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
从 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++ Cross-Platform High-Resolution Timer
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.htmlLinux x86-64 有一个
clock_gettime
系统调用。请注意,这不是一种防故障的微基准测试方法,因为:
man clock_gettime
表示如果您在程序运行时更改某些系统时间设置,此度量可能会出现不连续性。当然,这应该是一个罕见的事件,您可以忽略它。这会测量挂墙时间,因此如果调度程序决定忘记您的任务,它似乎会运行更长时间。
由于这些原因
getrusage()
,它可能是一个更好的 POSIX 基准测试工具,尽管它的最大精度较低。更多信息请参见:在 Linux 中测量时间 - time vs clock 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
, thetv_sec
是秒的一部分,而 thetv_usec
是microseconds ,而不是milliseconds。
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);