c++时间库

c++11提供的chrono库主要包含了三种类型:时间间隔Duration、时钟Clocks和时间点Time piont。这些类型都声明于头文件chrono,命名空间std::chrono中。

§duration

duration表示一段时间间隔,用来记录时间长度,可以表示几秒钟、几分钟或者几个小时的时间间隔,duration的原型是:

1
template<class Rep, class Period = std::ratio<1>> class duration;

第一个模板参数Rep是一个数值类型,表示时钟个数;第二个模板参数是一个默认模板参数std::ratio,表示每个时钟周期的秒数。它的原型是:

1
template<std::intmax_t Num, std::intmax_t Denom = 1> class ratio;

ratio是分数类型,其中第一个模板参数Num代表分子,Denom代表分母,分母默认为1。如ratio<2>代表一个时钟周期是两秒,ratio<60>代表了一分钟,ratio<60*60>代表一个小时,ratio<60*60*24>代表一天。而ratio<1, 1000>代表的则是1/1000秒即一毫秒,ratio<1, 1000000>代表一微秒,ratio<1, 1000000000>代表一纳秒。在头文件ratio中,定义了一些常用的分数,比如:

1
2
3
4
using nano = std::ratio<1, 1000000000>;
using milli = std::ratio<1, 1000>;
using kilo = std::ratio<1000, 1>;
// ...

标准库为了方便使用,定义了一些常用的时间间隔,如时、分、秒、毫秒、微秒和纳秒,在chrono命名空间下,它们的定义如下:

1
2
3
4
5
6
using nanoseconds = duration</* 至少 64 位的有符号整数类型 */ , nano>;
using microseconds = duration</* 至少 55 位的有符号整数类型 */ , micro>;
using milliseconds = duration</* 至少 45 位的有符号整数类型 */ , milli>;
using seconds = duration</* 至少 35 位的有符号整数类型 */ >;
using minutes = duration</* 至少 29 位的有符号整数类型 */ , ratio<60>>;
using hours = duration</* 至少 23 位的有符号整数类型 */ , ratio<3600>>;

通过定义这些常用的时间间隔类型,我们能更方便的定义和使用时长,比如线程的休眠时长:

1
2
std::this_thread::sleep_for(std::chrono::seconds(3)); //休眠三秒
std::this_thread::sleep_for(std::chrono:: milliseconds (100)); //休眠100毫秒

c++14还定义了它们的字面值常量类型:

1
2
3
4
5
6
operator""h	表示小时的 std::chrono::duration 字面量
operator""min	表示分钟的 std::chrono::duration 字面量
operator""s	表示秒的 std::chrono::duration 字面量
operator""ms	表示毫秒的 std::chrono::duration 字面量
operator""us	表示微秒的 std::chrono::duration 字面量
operator""ns	表示纳秒的 std::chrono::duration 字面量

使用count可以获取时间间隔的数量,例如:

1
2
3
4
std::chrono::milliseconds ms{3}; // 3 毫秒
std::chrono::microseconds us = 2 * ms; // 6000微秒
std::cout << ms.count() << std::endl;  // 3
std::cout << us.count() << std::endl;  // 6000

时间间隔之前可以做运算,例如:

1
2
3
std::chrono::minutes t1(10);  // 10分钟
std::chrono::seconds t2(60);      // 60秒
std::chrono::seconds t3 = t1 - t2;  // 540秒

使用std::chrono::duration_cast可以进行单位转换:

1
auto t4 = std::chrono::duration_cast<std::chrono::minutes>(t3);  // 9分钟

§time_point

time_point用于表示一个时间点,用来获取当前时间,可以做一些时间的比较和算术运算,也可以和ctime库结合起来显示时间(c++20提供日历库,可以代替ctime显示时间)。time_point必须要clock来计时,time_point有一个函数time_since_epoch()用来获得1970年1月1日到time_point时间经过的duration。下面的例子计算当前时间距离1970年1月1日有多少天。

1
2
3
4
using days = std::chrono::duration<int, std::ratio<60 * 60 * 24>>;
std::chrono::time_point<std::chrono::system_clock, days> today;
today = std::chrono::time_point_cast<days>(std::chrono::system_clock::now());
std::cout << today.time_since_epoch().count() << std::endl;

其中,time_point的声明为:

1
2
3
4
template<
    class Clock,
    class Duration = typename Clock::duration
> class time_point;

time_point与一个时钟类型和一个时长类型相关,这是因为时间点一般通过时钟获取而不是凭空构造(需要区分时间点和日历类型,前者用于计算与控制,后者给人看,因此日历或者日期可人为构造,而时间点则不能)。Duration是两个时间点之间的间隔的类型,如果设置了此模板参数且不为Clock::duration,则由Clock::now()获得的时间点无法直接赋值,必须使用time_point_cast进行转换:

1
2
3
4
5
std::chrono::time_point<
        std::chrono::system_clock,
        std::chrono::duration<double, std::ratio<1, 1000>>
> start;    // 以毫秒为计时单位
start = std::chrono::steady_clock::now();  // error: now()返回的时间点和start的duration不一样

time_point的构造函数为:

1
2
3
time_point();   // 构造表示Clock的纪元(即time_since_epoch为0)的时间点
time_point(const duration &d);  // 构造Clock的纪元加上d的时间点
template<class Duration2> time_point(const time_point<Clock,Duration2> &t);  // 通过转换t为duration构造time_point。此构造函数仅在Duration2可隐式转换为duration才参与重载决议。

time_point_cast的作用是将一个time_point转换成另一个,它们的模板参数只有duration不同。time_point_cast的声明为:

1
2
3
template <class ToDuration, class Clock, class Duration>
time_point<Clock, ToDuration> time_point_cast(
                                 const time_point<Clock, Duration> &t);

注意:只有当ToDuration是duration的实例,time_point_cast才会参与重载决议。

time_point还支持一些算术运算,可以加上或者减去某个duration得到另一个时间点,例如下面的代码将输出后一天的日期:

1
2
3
4
5
6
// 输出后一天的日期
std::chrono::time_point<std::chrono::system_clock> now;
now = std::chrono::system_clock::now();
auto next = now + std::chrono::hours(24);
std::time_t tomorrow = std::chrono::system_clock::to_time_t(next);
std::cout << std::ctime(&tomorrow) << std::endl;

结果类似于:Thu Aug 20 00:50:38 2020

§时钟

c++11的时钟类型有三个:system_clock、steady_clock、high_resolution_clock。

system_clock 表示系统范围的实时壁钟。它可以不单调:大多数系统上,系统时间可以在任何时候被调节。它是唯一有能力映射其时间点到 C 风格时间的 C++ 时钟。

steady_clock 表示单调时钟。此时钟的时间点无法减少,因为物理时间向前移动。此时钟与壁钟时间无关(例如,它能是上次重启开始的时间),且最适于度量间隔。

high_resolution_clock 表示实现提供的拥有最小计次周期的时钟。它可以是system_clock或steady_clock的别名,或第三个独立时钟。

注意:high_resolution_clock在不同标准库实现之间实现不一致,应该避免使用它。通常它是steady_clock或system_clock的别名,但实际是哪个取决于库或配置。它是system_clock时不是单调的(即时间能后退)。例如对于gcc的libstdc++它是system_clock,对于MSVC它是steady_clock,而对于clang的libc++它取决于配置。

通常用户应该直接使用steady_clock或system_clock代替high_resolution_clock:对时长度量使用steady_clock,对壁钟时间使用system_clock。

1
2
3
4
5
例如对代码进行计时:
auto start = std::chrono::steady_clock::now();
// ...
auto end = std::chrono::steady_clock::now();
std::cout << std::duration<double>(end - start).count() << "s" << std::endl;

需要注意时钟内部的跳数类型和计时周期是未定义的,c++标准规定的接口类似于:

1
2
3
4
5
6
7
8
9
class steady_clock {
public:
  using rep = /* 未指明 */ ;
  using period = ratio</* 未指明 */, /* 未指明 */ >;
  using duration = chrono::duration<rep, period>;
  using time_point = chrono::time_point</* 未指明 */, duration>;
  static constexpr bool is_steady = true;
  static time_point now() noexcept;
};

比如可能将rep定义为long long类型,而period定义为nanoseconds类型。因此在输出时长时必须对它作类型转换,可以定义一个临时的duration变量或者使用duration_cast进行转换,上面的代码定义了一个临时变量将它转换成double计数以s为单位的时长类型。

§计时器

在对程序进行性能测试时经常需要对某段代码进行时长计算,一般在要测试的代码前保存一下时间,要测试的代码后再保存一下时间,然后将两个时间相减即可得到代码的执行时长。将这个过程简单封装一下即可得到下面的计时器:

 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
34
class Timer {
public:
    Timer() : start{std::chrono::steady_clock::now()} {}

    // 默认按秒计时
    double elapsed() const {
        return elapsed_seconds();
    }
    
    // 按秒计时,直接转换成std::chrono::seconds会截断小数
    double elapsed_seconds() const {
        auto end = std::chrono::steady_clock::now();
        return std::chrono::duration<double, std::ratio<1>>(end - start).count();
    }
    
    // 按毫秒计时
    double elapsed_milliseconds() const {
        auto end = std::chrono::steady_clock::now();
        return std::chrono::duration<double, std::ratio<1, 1000>>(end - start).count();
    }
    
    // 按微秒计时
    double elapsed_microseconds() const {
        auto end = std::chrono::steady_clock::now();
        return std::chrono::duration<double, std::ratio<1, 1000000>>(end - start).count();
    }

private:
    std::chrono::steady_clock::time_point start;
};
使用方法为:
Timer timer;
// ...
std::cout << timer.elapsed() << std::endl;

§参考

[1] https://zh.cppreference.com/w/cpp/header/chrono.

[2] https://www.cnblogs.com/qicosmos/p/3642712.html.

加载评论