chrono_CLOCK(二)
文章目录
- chrono_CLOCK(二)
- 从测量C++程序运行时间引入
- C风格
- C++风格
- 时钟的成员和源码分析
- 成员函数
- 成员变量
- Clock提供的操作
- 例子
- 三个clock
- 区别
- 例子
- 三个clock的精度问题
- 方式一
- 方式二
从测量C++程序运行时间引入
C风格
在C程序和C++11以前的C++程序中,测量程序运行时间一般使用clock
函数和CLOCKS_PER_SEC
常量,定义在<time.h>
中。
clock_t
是一种能表示时钟周期数的算术类型,在MSVC和GCC中都是long
。
clock
函数返回自一个与程序执行相关的时间起至调用时刻经过的时钟周期数,类型为clock_t
。由于起始时间是由实现定义的,clock
函数的返回值没有直接的意义,只有两次调用clock
的结果之差才有意义。
CLOCKS_PER_SEC
表示一秒有多少个时钟周期,在MSVC和GCC中都是1000
,即C风格时间测量的精度为1毫秒。如果long
的大小是4字节,clock
溢出需要24天,一般情况下足够使用。
#include <stdio.h>
#include <time.h>
int work()
{
int sum = 0;
for (int i = 0; i < 1e8; ++i)
sum += i * i;
return sum;
}
int main()
{
clock_t start, finish;
start = clock();
volatile int result = work();
finish = clock();
printf("%fms\n", (double)(finish - start) / CLOCKS_PER_SEC * 1000);
}
C++风格
从C++11起,C++提供了更加现代的时间工具,定义在<chrono>
中,namespace std::chrono
下。
chrono
库主要定义了三种类型:时钟(clock)、时间点(time point)和时间段(duration)。时钟产生时间点,时间点相减得到时间段,时间点加减时间段得到时间点。由于有auto
自动类型推导和运算符重载的存在,我们在使用时很少需要写明与时间相关的变量的类型。
C++标准规定了3种时钟:
-
system_clock
,系统范围的挂钟,可以理解为桌面右下角的时钟,这个时钟是可以调节的,因此system_clock
的返回值可能不是单调的; -
steady_clock
,稳定的、单调的时钟,不受系统时间调节的影响,因而适合于测量时间间隔,通常测量程序运行时间就用steady_clock
; -
high_resolution_clock
,可用的最高精度的时钟,可以是上面两个的别名。 -
[ ]
-
每个时钟都有静态方法
now
返回当前的时间点,is_steady
常量表示时钟是否单调(steady_clock::is_steady
一定为true
)。system_clock
是唯一能与C中time_t
互通的时钟。 -
时钟定义了成员类型
period
,表示一个时钟周期的时长(以秒为单位)。在MSVC和GCC中,steady_clock::period
都是nano
,理论上的分辨率为纳秒。 -
两个时间点相减可以得到时间段,调用其
count
函数可以获得其数值,这个时间段的类型是由实现定义的。标准还定义了milliseconds
、seconds
等类型,为了得到以我们想要的单位表示的时间段,可以用duration_cast
来转换:
#include <iostream>
#include <chrono>
int work()
{
int sum = 0;
for (int i = 0; i < 1e8; ++i)
sum += i * i;
return sum;
}
int main()
{
auto start = std::chrono::steady_clock::now();
volatile int result = work();
auto finish = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(finish - start);
std::cout << duration.count() << "ms" << std::endl;
}
-
但是这样只能输出整数毫秒,如果想要更精确一点,一种方法是转换成
microseconds
以后除以1000.0
,更优雅地可以自己定义一种时间段类型,如duration<double, milli>
,其中double
表示这种时间段类型用double
来存储时钟周期数量,milli
表示时钟周期为1ms。从由整数表示的duration
到由浮点数表示的duration
的转换可以由duration
的构造函数来完成,无需再用duration_cast
: (就是浮点类型
和自定义deration提高精度
中的例子) -
改进的版本
// 自定义时间段类型,提高精度
void func2()
{
auto start = std::chrono::steady_clock::now();
volatile int result = work();
auto finish = std::chrono::steady_clock::now();
using myduration = std::chrono::duration<double, std::milli>;
myduration md = (finish - start);
// auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(finish - start);
std::cout << md.count() << "ms" << std::endl;
}
时钟的成员和源码分析
- 时钟是持续时间、 time _ point 和获取当前时间的静态函数的组合。
struct system_clock { // wraps GetSystemTimePreciseAsFileTime/GetSystemTimeAsFileTime
using rep = long long;
using period = ratio<1, 10'000'000>; // 100 nanoseconds
using duration = _CHRONO duration<rep, period>;
using time_point = _CHRONO time_point<system_clock>;
static constexpr bool is_steady = false;
_NODISCARD static time_point now() noexcept { // get current time
return time_point(duration(_Xtime_get_ticks()));
}
_NODISCARD static __time64_t to_time_t(const time_point& _Time) noexcept { // convert to __time64_t
return duration_cast<seconds>(_Time.time_since_epoch()).count();
}
_NODISCARD static time_point from_time_t(__time64_t _Tm) noexcept { // convert from __time64_t
return time_point{seconds{_Tm}};
}
};
成员函数
now[静态] | 返回表示时间中当前点的 std::chrono::time_point (公开静态成员函数) |
---|---|
to_time_t[静态] | 转换系统时钟时间点为 std::time_t (公开静态成员函数) |
from_time_t[静态] | 转换 std::time_t 到系统时钟时间点 (公开静态成员函数) |
成员变量
成员类型 | 定义 |
rep | 表示时钟时长中计次数的有符号算术类型 |
period | 表示时钟计次周期的 std::ratio 类型,单位为秒 |
duration | std::chrono::duration<rep, period> ,足以表示负时长 |
time_point | std::chrono::time_pointstd::chrono::system_clock |
(1)成员函数static time_point now() noexcept; 用于获取系统的当前时间。
(2)由于各种time_point表示方式不同,chrono也提供了相应的转换函数 time_point_cast。
template <class ToDuration, class Clock, class Duration> time_point<Clock,ToDuration> time_point_cast (const time_point<Clock,Duration>& tp);
传一个要转换为的精度的duration模板参数和一个要转换的time_point参数(用法见下面综合应用)
(3)其他成员函数:
to_time_t() time_point转换成time_t秒
from_time_t() 从time_t转换成time_point
Clock提供的操作
- 下图列出了clock提供的类型定义和static成
例子
- 输出当前时间,并且计算当前的时间距离1970年1月1日00:00的毫秒数
#include <iostream>
#include <chrono>
#include <ctime>
using namespace std;
int main()
{
//定义毫秒级别的时钟类型
typedef chrono::time_point<chrono::system_clock, chrono::milliseconds> microClock_type;
//获取当前时间点,windows system_clock是100纳秒级别的(不同系统不一样,自己按照介绍的方法测试),所以要转换
microClock_type tp = chrono::time_point_cast<chrono::milliseconds>(chrono::system_clock::now());
//转换为ctime.用于打印显示时间
time_t tt = chrono::system_clock::to_time_t(tp);
char _time[50];
ctime_s(_time,sizeof(_time),&tt);
cout << "now time is : " << _time;
//计算距离1970-1-1,00:00的时间长度,因为当前时间点定义的精度为毫秒,所以输出的是毫秒
cout << "to 1970-1-1,00:00 " << tp.time_since_epoch().count() << "ms" << endl;
system("pause");
return 0;
}
三个clock
-
标准库提供了三个clock:
-
system_clock
:它所表现的timepoint将关联至现行系统的即时时钟
- 这个clock提供便捷函数to_time_t()和from_time_t(),允许我们在timepoint和“C的系统时间类型”timet之间转换,这意味着你可以转换至日历时间
-
**strady_clock:**它保证绝不会被调用,因此当实际时间流逝,其timepoint值绝不会减少,而且这些timepoint相对于真实时间都有稳定的前进速率
-
**high_resolution_clock:**它所表现的是当前系统中带有最短tick周期的clock
-
-
这三个clock都支持上面Clock提供的操作
区别
- steady_clock 是单调的时钟,相当于教练手中的秒表;只会增长,
**适合用于记录程序耗时**
; - system_clock 是系统的时钟;因为系统的时钟可以修改;甚至可以网络对时; 所以用
系统时间计算时间差可能不准
。 - high_resolution_clock 是当前系统能够提供的最高精度的时钟;它也是不可以修改的。相当于
steady_clock 的高精度版本
。
例子
#include <iostream>
#include <chrono>
#include <thread>
using namespace std;
void func()
{
// 获得当前时间,保存为system_start
auto system_start = std::chrono::system_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(3s));
// 获得时间差
auto diff = std::chrono::system_clock::now() - system_start;
// 强制转换为秒
auto sec = std::chrono::duration_cast<std::chrono::seconds>(diff);
// 打印
std::cout << "this program runs:" << sec.count() << " seconds" << std::endl;
}
void func2()
{
// 获得当前时间,保存为system_start
auto steady_start = std::chrono::steady_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(3s));
// 获得时间差
auto diff = std::chrono::steady_clock::now() - steady_start;
// 强制转换为秒
auto sec = std::chrono::duration_cast<std::chrono::seconds>(diff);
// 打印
std::cout << "this program runs:" << sec.count() << " seconds" << std::endl;
}
int main()
{
func();
func2(); // 虽然打印出来的值是一样的但 ,如果中途修改了系统时间,也不影响now()的结果)
}
// steady_clock example
#include <iostream>
#include <ctime>
#include <ratio>
#include <chrono>
int main ()
{
using namespace std::chrono;
steady_clock::time_point t1 = steady_clock::now();
std::cout << "printing out 1000 stars...\n";
for (int i=0; i<1000; ++i) std::cout << "*";
std::cout << std::endl;
steady_clock::time_point t2 = steady_clock::now();
duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
std::cout << "It took me " << time_span.count() << " seconds.";
std::cout << std::endl;
return 0;
}
三个clock的精度问题
方式一
- 标准库并不强制规定上述clock的精准度、epoch,“最小和最大timepoint的范围”。举个例子,你的system clock也许提供的是UNIX epoch(1970年1月1日),如果你还需要一个特定的epoch,或你关注的timepoint并非被你的clock涵盖,你就必须使用各种便捷函数查清楚
- 例如,下面的函数打印某个clock的各种属性:
template<typename C>
void printClockData()
{
std::cout << "- precision: ";
typedef typename C::period P;
if (std::ratio_less_equal<P, milli>::value){
typedef typename std::ratio_multiply<P, kilo>::type TT;
std::cout << fixed << double(TT::num) / TT::den << " milliseconds" << std::endl;
}
else {
std::cout << fixed << double(P::num) / P::den << " seconds" << std::endl;
}
std::cout << "- si_steady: " << boolalpha << C::is_steady << std::endl;
}
- 我们可以针对各种clock调用这个函数,例如:
int main()
{
std::cout << "system_clock: " << std::endl;
printClockData<std::chrono::system_clock>();
std::cout << "\nhigh_resolution_clock: " << std::endl;
printClockData<std::chrono::high_resolution_clock>();
std::cout << "\nsteady_clock: " << std::endl;
printClockData<std::chrono::steady_clock>();
}
- 结果如下图所示:
- 可以看到,system_clock和high_resolution_clock有着相同精度,100纳秒,而steady_clock的精度则是毫秒
- 还可以看到,steady_clock和high_resolution_clock不能被调整
- 还需要注意,在其他系统中情况也许完全不同,例如high_resolution_clock有可能和system_clock相同
方式二
#include <iostream>
#include <chrono>
using namespace std;
int main()
{
cout << "system clock : ";
cout << chrono::system_clock::period::num << "/" << chrono::system_clock::period::den << "s" << endl;
cout << "steady clock : ";
cout << chrono::steady_clock::period::num << "/" << chrono::steady_clock::period::den << "s" << endl;
cout << "high resolution clock : ";
cout << chrono::high_resolution_clock::period::num << "/" << chrono::high_resolution_clock::period::den << "s" << endl;
system("pause");
return 0;
}