首图来自b站UP主: 双笙子佯谬
有时候我需要知道程序执行过程中某个模块的耗时,在网上查到需要用到 chrono
库的 system_clock
类,但是很多人都只是给出了代码片段,其中出现很多没见过的类和成员函数都不知道什么意思,既然没有这方面的中文文章,那我来写一篇吧,觉得这篇文章对你有用,可以点个赞,有了你的鼓励,我才会更有动力。
C++ 支持两种类型的时间操作:
chrono
库,包含一系列和时钟、日期相关的功能- C 样式的日期和时间库 (例如 std::time)
本篇文章介绍 chrono
库的使用,首先需要使用 #include <chrono>
包含这个库,所有类均在命名空间 std::chrono
下。
chrono
库里包括三种主要类型:clocks
,time points
和 durations
。
先扔一个目录在这:
- Clocks
- system_clock
- steady_clock
- high_resolution_clock
- Duration
- duration
- Time point
- time_point
Clocks
一个时钟(clock)包含了两部分:时钟原点和时钟节拍。时钟原点通常是 1970-01-01 00:00:00 UTC
,一段时间的时钟节拍数除以振动频率,可以得到这段时间的具体时间。
C++ 定义了三种类型的时钟:
std::chrono::system_clock
:本地系统的当前时间 (可以调整)std::chrono::steady_clock
:不能被调整的,稳定增加的时间std::chrono::high_resolution_clock
:提供最高精度的计时周期
system_clock
的时间值可以在操作系统内部进行调整,比如在上午九点,把时间改成下午六点,通常用于获取当前时间。
steady_clock
表示过去某个时间点到现在之间的时间,这个时间不能被调整,物理时间增加,它一定增加,而且与系统时间无关(可以是系统重启以来的时间段),最适合于计算程序的耗时时长。
high_resolution_clock
是一个高分辨率时钟,时间刻度最小。在不同的标准库中,high_resolution_clock 的实现不一致,所以官方不建议使用这个时钟,前两个时钟已经能够满足我们日常的使用要求了。
每一个 clock 类中都有确定的 time_point
, duration
, Rep
, Period
类型,这些类型后面都会讲到。
接下来详细介绍前两个时钟。
这两个时钟类都提供了一个静态成员函数 now()
用于获取当前时间,该函数的返回值都是 time_point
类型,system_clock 还另外提供了两个支持系统时间和 std::time_t 相互转换的静态成员函数: to_time_t()
和 from_time_t()
。
先看一个很简单的例子,用于获取时间间隔:
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono;
int main() {
// "start" and "end"'s type is std::chrono::time_point
time_point<system_clock> start = system_clock::now();
{
std::this_thread::sleep_for(std::chrono::seconds(2));
}
time_point<system_clock> end = system_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::cout << "Elapsed time: " << elapsed.count() << "s";
return 0;
}
sleep_for
用于使主线程休眠,休眠时间由 std::chrono::seconds()
控制,std::chrono::duration
后面讲解。
运行后可以得到结果为 2.0003
,很接近 2 秒。可以将 system_clock 改为 steady_clock,得到的结果基本相同。
上面是打印时间间隔,还可以查看系统当前时间,
#include <chrono>
#include <string>
#include <iostream>
int main() {
auto t = std::chrono::system_clock::now();
std::time_t tt = std::chrono::system_clock::to_time_t(t);
std::string stt = std::ctime(&tt);
std::cout << stt;
std::tm* now = std::gmtime(&tt);
printf("%4d年%02d月%02d日 %02d:%02d:%02d\n",
now->tm_year+1900, now->tm_mon+1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec);
return 0;
}
输出内容为:
Thu Sep 25 20:32:38 2020
2020年9月25日 20:32:38
Duration
std::chrono::duration
也是一个模板类,从名字都可以看出来,它表示一个时间间隔。
template<
class Rep,
class Period = std::ratio<1>
> class duration;
使用参数 Rep
和 Period
共同表示时间间隔。Rep 用于指定数值类型,表示节拍的数量,比如 int,float 等;
Period 属于 std::ratio
类型,表示单位精度,用来计算一个节拍的周期,std::ratio 是一个分数类型的模板类,包含分子(Num)和分母(Denom)两部分。
template<
std::intmax_t Num,
std::intmax_t Denom = 1
> class ratio;
通过 Num/Denom
来得到节拍的周期,注意时间单位默认为秒。duration 默认 std::ratio 的分子为 1,std::ratio 默认分母为 1。
这时再来解释之前出现过的语句:
std::chrono::duration<double> elapsed = end - start;
std::cout << "Elapsed time: " << elapsed.count() << "s";
这里定义了一个变量 elapsed,duration 类型为 double,计算节拍周期的分子分母都默认为 1,那么周期就是 1 秒;然后通过 count()
函数来得到节拍的个数,所以输出结果为 2.0003
。
在 chrono 中预定义了一系列常用的 duration,可以直接使用:
std::chrono::nanoseconds duration<long long, std::ratio<1, 1000000000>>
std::chrono::microseconds duration<long long, std::ratio<1, 1000000>>
std::chrono::milliseconds duration<long long, std::ratio<1, 1000>>
std::chrono::seconds duration<long long>
std::chrono::minutes duration<int, std::ratio<60>>
std::chrono::hours duration<int, std::ratio<3600>>
以 std::chrono::minutes 为例,Period 为 std::ratio<60> 表示分子为 60,分母默认为 1,那么周期就是 60 秒,所以 std::chrono::minutes(5) 就代表 5 分钟。
这也是刚才上面用到 std::chrono::seconds(2)
的由来,可以看到最小精度为可以达到纳秒级别。
想要将 std::chrono::duration
在不同的精度之间进行转换时,可以使用 std::chrono::duration_cast
。
#include <iostream>
#include <chrono>
#include <ratio>
#include <thread>
using namespace std::chrono;
int main()
{
auto t1 = system_clock::now();
std::this_thread::sleep_for(seconds(1));
auto t2 = system_clock::now();
// floating-point duration: no duration_cast needed
duration<double, std::milli> fp_ms = t2 - t1;
// integral duration: requires duration_cast
auto int_ms = duration_cast<milliseconds>(fp_ms);
// converting integral duration to integral duration of shorter divisible time unit: no duration_cast needed
duration<long, std::micro> int_usec = int_ms;
std::cout << "took " << fp_ms.count() << " ms, "
<< "or " << int_ms.count() << " whole milliseconds "
<< "(which is " << int_usec.count() << " whole microseconds)" << std::endl;
}
得到 fp_ms 和 int_usec 时并没有用 duration_cast,那是因为有两种情况可以不使用 duration_cast,而使用 std::chrono::duration
进行隐式类型转换:
- 当两者的数值类型为整型,且是大单位转小单位(比如秒转毫秒);
- 当两者的数值类型都是浮点型;
Time point
std::chrono::time_point
是一个表示具体时间点的类模板,可以表示距离时钟原点的时间长度(duration)。
template<
class Clock,
class Duration = typename Clock::duration
> class time_point;
其中的 Clock 用来指定要使用的时钟,比如要使用的 system_clock,steady_clock;
Duration 表示从时钟原点开始到现在的时间间隔,可以设置不同的时间计量单位。
time_point
表示一个时间点,通过 time_since_epoch()
函数就可以得到该时间点到时钟原点的时间间隔。
#include <iostream>
#include <chrono>
using namespace std::chrono;
int main() {
// now 表示当前时间到时钟原点的毫秒数
time_point<system_clock> now = system_clock::now();
auto elapsed = duration_cast<std::chrono::hours>(now.time_since_epoch());
std::cout << elapsed.count() << " 小时" << std::endl;
std::cout << now.time_since_epoch().count() << " 毫秒"<< std::endl;
return 0;
}
定义 now 时传入 system_clock
为第一个参数,使用 duration_cast
将毫秒单位转换为小时单位。
time_point 想要进行精度转换,则使用 std::chrono::time_point_cast
,用法和 duration_cast 相同,区别就在于输出参数从传入 duration 改为 time_point,如下例子所示。
#include <iostream>
#include <chrono>
using namespace std::chrono;
int main() {
auto t1 = system_clock::now();
auto t2 = time_point_cast<seconds>(t1);
std::cout << t1.time_since_epoch().count() << " 毫秒" << std::endl;
std::cout << t2.time_since_epoch().count() << " 秒" << std::endl;
return 0;
}
好了,关于 chrono 的常见用法就是这些,无非就是三部分:表示时钟的clock,表示时间间隔的 duration 和表示时间点的 time_point。
当我们想获取系统时钟或者衡量一段代码运行时间时,就可以考虑使用 chrono 这个神奇。
本次只介绍了 chrono 中 C++11 的类和函数,如果想查看更高 C++ 版本的相关类和函数,可以查看网站: