C++11 chrono库获取时间 精度

通过菜鸟测试分析chrono的精确度

1秒(s) = 100分秒(ds) = 1000 毫秒(ms) = 1e6 微秒(μs) = 1e9 纳秒(ns) = 1e12 皮秒(ps) = 1e15 飞秒(fs) = 1e18 阿秒(as) = 1e21 仄秒(zs) = 1e24 幺秒(ys)

前言

因为在项目中使用了C++11 chrono库的system_clock:

1
#define GET_TIME_STAMP std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()

来获取时间戳用以计时,看着很复杂,有时间来分析一下chrono的精确度。

铺垫

语言层面上的时间函数都或多或少存在误差,翻阅资料后决定通过计算机的高精度计数器HPET来计算运行时间。

1
2
3
4
5
6
7
BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
// 获取高精度计数器的时钟频率
BOOL QueryPerformanceCounter (LARGE_INTEGER *lpCount);
// 获取从上电以来计数器计数

during = (Count_end.QuadPart - Count_begin.QuadPart) / freq.QuadPart * 1e9;
// 获取以纳秒为单位的时间

测试

了解过程中发现chrono除了system_clock还有一个steady_clock,有了以下测试代码:

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 <Windows.h>
#include <chrono>
#include <iostream>
#include <iomanip>

int main()
{
LARGE_INTEGER HPET_begin, HPET_end;
auto t_sysclk_begin = chrono::system_clock::now();
auto t_steadyclk_begin = chrono::steady_clock::now();
QueryPerformanceCounter(&HPET_begin);

Sleep(1000);

auto t_sysclk_end = chrono::system_clock::now();
auto t_steadyclk_end = chrono::steady_clock::now();
QueryPerformanceCounter(&HPET_end);

cout << fixed;
cout << setprecision(0);

auto d = chrono::duration_cast<chrono::nanoseconds>(t_sysclk_end - t_sysclk_begin);
// 计算精度设置chrono::nanoseconds,还有microseconds ~ hours,方便!
cout << "chrono::system_clock: " << d.count() << "ns" << endl;
auto d3 = chrono::duration_cast<chrono::nanoseconds>(t_steadyclk_end - t_steadyclk_begin);
cout << "chrono::steady_clock: " << d3.count() << "ns" << endl;

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
cout << "HPET clock: " << (double)(HPET_end.QuadPart - HPET_begin.QuadPart) / freq.LowPart * 1e9<< "ns" << endl;
cout << "HPET freq: " << freq.LowPart << "; 理论误差:" << 1.0 / freq.LowPart * 1e9 << "ns" << endl;
return 0;
}

中间用Sleep(1000)延时1秒,以计时,输出如下:

1
2
3
4
chrono::system_clock: 1000613300ns
chrono::steady_clock: 1000610323ns
HPET clock: 1000610689ns
HPET freq: 2728067; 理论误差:367ns

理论误差是指HPET clock获取的时间理想情况下误差在一次计数的时间(1/freq)以内。

从输出结果可以看到

chrono::system_clock的精度是100ns

chrono::steady_clock的精度达到了1ns

推测

通过测试发现steady_clock得到的值多次与HPET计算值相同,其余都是相差一个误差,因此推测steady_clock也是通过高精度计数器计算的。

但是网上找不到相关的说法,只好看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// chrono.h
struct steady_clock
{ // wraps QueryPerformanceCounter
……
_NODISCARD static time_point now() noexcept
{ // get current time
const long long _Freq = _Query_perf_frequency(); // doesn't change after system boot
const long long _Ctr = _Query_perf_counter();
……
}
};

using high_resolution_clock = steady_clock;
}

调用了MS的_Query_perf_counter(); 看不到具体实现,网上居然也找不到直接的介绍,猜测跟QueryPerformanceCounter有关,保留猜测。

其实chrono还有一个clock:high_resolution_clock

using high_resolution_clock = steady_clock;

其实就是steady_clock。

总结

chrono::system_clock 可以获取时钟时间,精度到100ns。

chrono::steady_clock 形似高精度计数器,精度1ns。稳定增加,是chrono指定**適於度量間隔**的clock。


附加

偶然发现boost库有一个计时器cpu_timer,顺便测了一下

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
#include <boost/timer/timer.hpp>
#include <Windows.h>
#include <iostream>
#include <iomanip>
int main()
{
boost::timer::cpu_timer boost_timer;
LARGE_INTEGER cpu_begin, cpu_end;
boost_timer.start();
QueryPerformanceCounter(&cpu_begin);

Sleep(1000);

boost_timer.stop();
QueryPerformanceCounter(&cpu_end);

cout << fixed;
cout << setprecision(0);
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
cout << "Boost::cpu_timer: " << boost_timer.format(9) << endl;
cout << "Cpu clock: " << (double)(cpu_end.QuadPart - cpu_begin.QuadPart) / freq.LowPart * 1e9<< "ns" << endl;
cout << "cpu freq: " << freq.LowPart << "; 理论误差:" << 1.0 / freq.LowPart * 1e9 << "ns" << endl;

return 0;
}

输出:

1
2
3
Boost::cpu_timer:    1.000058283s wall, 0.00s user + 0.00s system = 0.00s CPU (n/a%)
Cpu clock: 1000061949ns
cpu freq: 2728067; 理论误差:367ns

比起自己通过chrono获取时间算的误差大多了,足足有 4-10us,官方倒是对此有解释:

Intel Core – 366ns – Some variation, usually in multiples of 366ns
reference

通常是366ns的倍数。