在不同的平台有繁多的 Time API,如何选用精准的高精度 Time 函数来做
performance benchmarking 呢?
Wall-clock time VS CPU time
先理解一些时间的概念。明白不同时间 API 测量的是什么时间。
Wall-clock time ,顾名思义,墙上的钟,代表一个任务从开始到完成所经历的时间。它包含 3 部分:CPU 的时间,I/O 的时间和通信延迟的时间。但 wall-clock 很少是正确的时钟来使用,因为它随着时区,和 daylightsaving 改变,或与 NTP 同步。而这些特性没有一个是有益的,如果你用它来调度任务或做 performance benchmarking。它仅仅如名字所言,墙上的一个时钟。
CPU time 仅仅统计一个任务从开始到完成在 CPU 上所花的时间。CPU time 主要包括 User time(在 user space 所花时间)和 System time(在 kernel space 所花时间)。
以并行程序为例,CPU time 就是所有 CPU 在这个程序所花的时间总和,
Wall-clock time 在这种情况可能时间相对短,它只统计任务开始到结束所花时间。
不同时钟 API 对比1
对于不同的时钟 API,主要分析如下特性:
API 测试的是什么时间?(real, user, system,CPU or wall-clock)
API 的精度?(s, ms, µs, or faster?)
多久时间这个时钟数字会返转?或有什么策略避免它?
时钟是 monotonic 的,还是它会随着系统时间改变(比如 NTP,time zone,
daylight saving time, by the user, etc)?
Linux 和 OS X 的主要时钟 API:
time() 返回系统的 wall-clock,精度到秒。
clock() 返回 user 和 systime 总共的时间.现在标准要求CLOCKS_PER_SEC
是1000000
,使精度最多达到
1µs.clock_t
类型平台相关(The range and precision of times
representable in clock_t and time_t are implementation-defined.) 它
wrap around 一旦达到最大值.(通常是 32 位的类型,那么~2^32 ticks 后,还是比较长的时间.)
clock_gettime(CLOCK_MONOTONIC,..)
提供纳秒级的精确度并且是单调的.它的秒和纳秒是分开存储的,所以,任何的
wrap around 将很多年才发生一次.它是个不错的时钟,但 OS X 平台上没有.
getrusage 返回独立的 user 和
system 时间,并且不会 wrap around.精确达到 1 µs,
gettimeofday 返回一个
wall-clock 时间并达到µs 精度.但是精度不能保证,因为依赖于硬件 .
mach_absolute_time()
是 OS X 平台的高精度(ns)计时的一个选择.ns 以 64 位 unsigned integer 存储,实际使用 wrap around 不是大问题,移植性是问题.
Window 的高精度时钟:
QueryPerformanceFrequency()
和
QueryPerformanceCounter() .
QueryPerformanceFrequency() 返回计数的频率,QueryPerformanceCounter()返回当前计数值.和 Linux 中 CLOCK_MONOTONIC 一样,它是一个稳定并单调递增计数器,精准达到纳秒级,并且不会 wrap around.
更多参考:
不同平台 High Resolution Time
Linux
使用
clock_gettime(CLOCK_MONOTONIC,..)
作为 High Resolution Time,编译需加上参数-lrt
,实例代码如下:
clock_gettime.c 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <time.h>
#include <stdio.h>
void GetMonotonicTime ( struct timespec * ts ) {
clock_gettime ( CLOCK_MONOTONIC , ts );
}
double GetElapsedTime ( struct timespec * before , struct timespec * after ) {
double delta_s = after -> tv_sec - before -> tv_sec ;
double delta_ns = after -> tv_nsec - before -> tv_nsec ;
return delta_s * 1e9 + delta_ns ;
}
int main ( int argc , char * argv []) {
struct timespec before , after ;
GetMonotonicTime ( & before );
double sum = 0.0 ;
unsigned int i ;
for ( i = 1 ; i < 100 ; ++ i ) {
sum += 1.0 / i ;
}
GetMonotonicTime ( & after );
printf ( "the elapsed time=%e ns \n " , GetElapsedTime ( & before , & after ));
return 0 ;
}
除了clock_gettime()
高精度时钟外,还有相对应的高精度的睡眠函数
clock_nanosleep ,
实例代码如下:
clock_nanosleep.c 1
2
3
4
5
6
7
8
9
10
#include <time.h>
int main ( int argc , char * argv [])
{
struct timespec sleep_time ;
sleep_time . tv_sec = 0 ;
sleep_time . tv_nsec = 100 ;
clock_nanosleep ( CLOCK_REALTIME , 0 , & sleep_time , NULL );
return 0 ;
}
OS X
使用clock_get_time
clock_get_time.c 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <time.h>
#include <stdio.h>
#include <mach/clock.h>
#include <mach/mach.h>
void GetMonotonicTime ( struct timespec * ts ) {
clock_serv_t cclock ;
mach_timespec_t mts ;
host_get_clock_service ( mach_host_self (), SYSTEM_CLOCK , & cclock );
clock_get_time ( cclock , & mts );
mach_port_deallocate ( mach_task_self (), cclock );
ts -> tv_sec = mts . tv_sec ;
ts -> tv_nsec = mts . tv_nsec ;
}
double GetElapsedTime ( struct timespec * before , struct timespec * after ) {
double delta_s = after -> tv_sec - before -> tv_sec ;
double delta_ns = after -> tv_nsec - before -> tv_nsec ;
return delta_s * 1e9 + delta_ns ;
}
int main ( int argc , char * argv []) {
struct timespec before , after ;
GetMonotonicTime ( & before );
double sum = 0.0 ;
unsigned int i ;
for ( i = 1 ; i < 100 ; ++ i ) {
sum += 1.0 / i ;
}
GetMonotonicTime ( & after );
printf ( "the elapsed time=%e ns \n " , GetElapsedTime ( & before , & after ));
return 0 ;
}
使用mach_absolute_time
mach_absolute_time.c 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main ( int argc , char * argv []) {
uint64_t start ;
uint64_t end ;
uint64_t elapsed ;
Nanoseconds elapsedNano ;
start = mach_absolute_time ();
double sum = 0.0 ;
unsigned int i ;
for ( i = 1 ; i < 100 ; ++ i ) {
sum += 1.0 / i ;
}
end = mach_absolute_time ();
elapsed = end - start ;
// Convert to nanoseconds
elapsedNano = AbsoluteToNanoseconds ( * ( AbsoluteTime * ) & elapsed );
}
Windows
query_performance.cc 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <windows.h>
using namespace std ;
int main ()
{
LARGE_INTEGER frequency ;
LARGE_INTEGER start , end ;
double elapsedTime ;
// get ticks per second
QueryPerformanceFrequency ( & frequency );
QueryPerformanceCounter ( & start );
//do someting
double sum = 0.0 ;
unsigned int i ;
for ( i = 1 ; i < 100 ; ++ i ) {
sum += 1.0 / i ;
}
QueryPerformanceCounter ( & end );
// compute and print the elapsed time in millisec
elapsedTime = ( end . QuadPart - start . QuadPart ) * 1000.0 / frequency . QuadPart ;
cout << elapsedTime << " ms. \n " ;
return 0 ;
}