C++进阶:chrono之clock

800 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

概述

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

C++20前,不指定 system_clock 的纪元,它多数情况下实现使用 Unix 时间(即从协调世界时 (UTC) 1970 年 1 月 1 日星期四 00:00:00 开始的时间,不计闰秒)。C++20后,system_clock 确定就是使用 Unix 时间。

chrono 库中提供了获取当前的系统时间的时钟类,包含的时钟一共有三种:

  • system_clock:系统的时钟,系统的时钟可以修改,甚至可以网络对时,因此使用系统时间计算时间差可能不准。
  • steady_clock:是固定的时钟,相当于秒表。开始计时后,时间只会增长并且不能修改,适合用于记录程序耗时
  • high_resolution_clock:和时钟类 steady_clock 是等价的(是它的别名)。

在这些时钟类的内部有 time_point、duration、Rep、Period 等信息,基于这些信息来获取当前时间,以及实现 time_t 和 time_point 之间的相互转换。

system_clock

成员类型

时钟类成员类型描述
rep表示时钟周期次数的有符号算术类型,类型是 long long
period表示时钟计次周期的 std::ratio 类型,单位是100 纳秒 ratio<1, 10'000'000>
duration时间间隔,可以表示负时长,时间间隔为 rep*period 纳秒 chrono::duration<rep, period>
time_point表示在当前时钟里边记录的时间点,时间点通过系统时钟做了初始化 chrono::time_point<system_clock>

成员函数

// 返回表示当前时间的时间点。
static std::chrono::time_point<std::chrono::system_clock> now() noexcept;
// 将 time_point 时间点类型转换为 std::time_t 类型
static std::time_t to_time_t( const time_point& t ) noexcept;
// 将 std::time_t 类型转换为 time_point 时间点类型
static std::chrono::system_clock::time_point from_time_t( std::time_t t ) noexcept;

示例:

#include <chrono>
#include <iostream>
​
using namespace std;
using namespace std::chrono;
​
int main()
{
    // 新纪元1970.1.1时间
    system_clock::time_point epoch;
​
    duration<int, ratio<60*60*24>> day(1);
    // 新纪元1970.1.1时间 + 1天
    system_clock::time_point ppt(day);
​
    using dday = duration<int, ratio<60 * 60 * 24>>;
    // 新纪元1970.1.1时间 + 10天
    time_point<system_clock, dday> t(dday(10));
​
    // 系统当前时间
    system_clock::time_point today = system_clock::now();
    
    // 转换为time_t时间类型
    time_t tm = system_clock::to_time_t(today);
    cout << "today:       " << ctime(&tm);
​
    time_t tm1 = system_clock::to_time_t(today+day);
    cout << "tommorow:    " << ctime(&tm1);
​
    time_t tm2 = system_clock::to_time_t(epoch);
    cout << "epoch:      " << ctime(&tm2);
​
    time_t tm3 = system_clock::to_time_t(ppt);
    cout << "epoch+1d:   " << ctime(&tm3);
​
    time_t tm4 = system_clock::to_time_t(t);
    cout << "epoch+10d: " << ctime(&tm4);
}

输出:

today:       Wed Jul  6 23:00:14 2022
tommorow:    Thu Jul  7 23:00:14 2022
epoch:      Thu Jan  1 00:00:00 1970
epoch+1d:   Fri Jan  2 00:00:00 1970
epoch+10d: Sun Jan 11 00:00:00 1970

steady_clock

如果我们通过时钟不是为了获取当前的系统时间,而是进行程序耗时的时长,此时使用 syetem_clock 就不合适了(外部可以进行修改),因为这个时间可以跟随系统的设置发生变化。在 C++11 中提供的时钟类 steady_clock 相当于秒表,只要启动就会进行时间的累加,并且不能被修改,非常适合于进行耗时的统计。

成员类型

成员类型跟system_clock一样,但是具体细节有些差异,如下:

时钟类成员类型描述
rep表示时钟周期次数的有符号算术类型,类型是 long long
period表示时钟计次周期的 std::ratio 类型,单位是1纳秒 ratio<1, 1000'000'000>
duration时间间隔,为1纳秒 chrono::duration<rep, period>
time_point表示在当前时钟里边记录的时间点,时间点通过系统时钟做了初始化 chrono::time_point<steady_clock>

示例:

#include <chrono>
#include <iostream>int main() {
    for (auto size = 1ull; size < 1000000000ull; size *= 100) {
        // record start time
        auto start = std::chrono::steady_clock::now();
        // do some work
        std::cout << "do something ...." << std::endl;
        for (auto i = 0; i < 1000000; i++);   //do something
        // record end time
        auto end = std::chrono::steady_clock::now();
        std::chrono::duration<double> diff = end - start;
        std::cout << "Time use "<< diff.count() << " s\n";
    }
}

high_resolution_clock

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

high_resolution_clock 在不同标准库实现之间实现不一致,应该尽量避免使用它。通常它只是 std::chrono::steady_clockstd::chrono::system_clock 的别名,但实际是哪个取决于库或配置。它是 system_clock 时不是单调的(即时间能后退)。例如对于 gcc 的 libstdc++ 它是 system_clock ,对于 MSVC 它是 steady_clock ,而对于 clang 的 libc++ 它取决于配置。通常用户应该直接使用 std::chrono::steady_clock 或 std::chrono::system_clock 代替 std::chrono::high_resolution_clock ;对时长度量使用 steady_clock ,对壁钟时间使用 system_clock

其他的成员类型函数跟上面两哥们基本差不多,主要还是使用now方法:

#include <chrono>
#include <iostream>int main() {
    for (auto size = 1ull; size < 1000000000ull; size *= 100) {
        // record start time
        auto start = std::chrono::high_resolution_clock::now();
        // do some work
        std::cout << "do something ...." << std::endl;
        for (auto i = 0; i < 1000000; i++);   //do something
        // record end time
        auto end = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double> diff = end - start;
        std::cout << "Time use "<< diff.count() << " s\n";
    }
}

总的来说:

  • 对于system_clock,其起点是epoch,即1970-01-01 00:00:00 UTC,其刻度是1个tick,也就是_XTIME_NSECS_PER_TICK纳秒(一般是100纳秒)。
  • steady_clock的刻度是1纳秒,起点并非1970-01-01 00:00:00 UTC,一般是系统启动时间,这就是问题的关键。steady_clock的作用是为了得到不随系统时间修改而变化的时间间隔,所以凡是想得到绝对时点的用法都是错误的。steady_clock是没有to_time_t()的实现的,而system_clock是有的。
  • high_resolution_clock实际上和steady_clock一样。

一般使用场景是:

  • system_clock:用在需要得到绝对时点的场景
  • steady_clock:用在需要得到时间间隔,并且这个时间间隔不会因为修改系统时间而受影响的场景
  • high_resolution_clock:high_resolution_clock是system_clock或steady_clock之一,根据情况使用

clock时钟相关内容就到这里了。