C++ `std::chrono` 计时库的高级用法:时间点、时长与时钟精度

哈喽,各位好!今天咱们来聊聊C++ std::chrono 计时库,这玩意儿听起来高大上,其实就是个帮你精确测量时间的工具。就像你在厨房里用的计时器,只不过它更高级,更精确,而且能玩出更多花样。

咱们今天要讲的主要有三个方面:时间点(time_point)、时长(duration)和时钟精度(clock)。这三者是std::chrono的核心,理解了它们,你就能像个时间魔法师一样,在你的程序里自由地操纵时间。

1. 时长(duration):时间流逝的长度

时长,顾名思义,就是一段时间的长度。比如,你说“我睡了8个小时”,这里的“8个小时”就是一个时长。在std::chrono里,时长用std::chrono::duration来表示。

std::chrono::duration的定义方式是这样的:

std::chrono::duration<Rep, Period>
  • Rep (Representation): 表示时长所用的数值类型,比如int, long long, double等等。默认是int
  • Period (Ratio): 表示Rep所代表的时间单位。它是一个std::ratio类型,表示一个编译时有理数。

举个例子,std::chrono::seconds 实际上是 std::chrono::duration<long long, std::ratio<1, 1>> 的一个别名,也就是说,它用 long long 来存储秒数,并且每个 long long 代表 1 秒。 std::chrono::milliseconds 则是 std::chrono::duration<long long, std::ratio<1, 1000>> 的别名,表示毫秒。

常用的预定义时长类型:

类型 单位 描述
std::chrono::nanoseconds 纳秒 1 秒的十亿分之一 (1/1,000,000,000 秒)
std::chrono::microseconds 微秒 1 秒的百万分之一 (1/1,000,000 秒)
std::chrono::milliseconds 毫秒 1 秒的千分之一 (1/1,000 秒)
std::chrono::seconds 基本的时间单位
std::chrono::minutes 分钟 60 秒
std::chrono::hours 小时 3600 秒 (60 分钟)

创建时长对象:

#include <iostream>
#include <chrono>

int main() {
    // 创建一个表示 5 秒的时长
    std::chrono::seconds five_seconds(5);

    // 创建一个表示 100 毫秒的时长
    std::chrono::milliseconds hundred_milliseconds(100);

    // 创建一个表示 2.5 秒的时长 (注意使用 double)
    std::chrono::duration<double, std::ratio<1, 1>> two_and_half_seconds(2.5);

    std::cout << "Five seconds: " << five_seconds.count() << " seconds" << std::endl;
    std::cout << "Hundred milliseconds: " << hundred_milliseconds.count() << " milliseconds" << std::endl;
    std::cout << "Two and a half seconds: " << two_and_half_seconds.count() << " seconds" << std::endl;

    return 0;
}

时长对象的常用操作:

  • count(): 返回时长所代表的数值。注意,返回值的类型取决于Rep
  • 加减乘除: 时长对象可以进行加减乘除运算,得到新的时长对象。
  • 比较: 时长对象可以进行比较运算(==, !=, <, >, <=, >=)。
  • 类型转换: 可以使用std::chrono::duration_cast将一个时长转换为另一个时长。
#include <iostream>
#include <chrono>

int main() {
    std::chrono::seconds ten_seconds(10);
    std::chrono::milliseconds five_hundred_milliseconds(500);

    // 加法
    auto total_time = ten_seconds + five_hundred_milliseconds;
    std::cout << "Total time: " << total_time.count() << " milliseconds" << std::endl; // 输出: 10500

    // 减法
    auto remaining_time = ten_seconds - std::chrono::seconds(3);
    std::cout << "Remaining time: " << remaining_time.count() << " seconds" << std::endl; // 输出: 7

    // 乘法
    auto doubled_time = ten_seconds * 2;
    std::cout << "Doubled time: " << doubled_time.count() << " seconds" << std::endl; // 输出: 20

    // 类型转换
    auto ten_seconds_in_ms = std::chrono::duration_cast<std::chrono::milliseconds>(ten_seconds);
    std::cout << "Ten seconds in milliseconds: " << ten_seconds_in_ms.count() << " milliseconds" << std::endl; // 输出: 10000

    // 比较
    if (ten_seconds > std::chrono::seconds(5)) {
        std::cout << "Ten seconds is greater than five seconds" << std::endl;
    }

    return 0;
}

duration_cast的坑:

duration_cast用于在不同的时长类型之间进行转换。但是,如果目标类型的精度低于源类型,可能会发生截断。

#include <iostream>
#include <chrono>

int main() {
    std::chrono::duration<double, std::ratio<1, 1>> fractional_seconds(2.7);

    // 转换为整数秒,小数部分会被截断
    auto integer_seconds = std::chrono::duration_cast<std::chrono::seconds>(fractional_seconds);
    std::cout << "Integer seconds: " << integer_seconds.count() << " seconds" << std::endl; // 输出: 2 (截断了 .7)

    return 0;
}

所以,在使用duration_cast的时候,一定要小心精度损失!

2. 时间点(time_point):时间轴上的一个瞬间

时间点表示时间轴上的一个特定时刻。 比如,你说“今天早上8点”,这里的“今天早上8点”就是一个时间点。 在std::chrono里,时间点用std::chrono::time_point来表示。

std::chrono::time_point的定义方式是这样的:

std::chrono::time_point<Clock, Duration>
  • Clock: 表示时间点所使用的时钟。不同的时钟有不同的起点和精度。
  • Duration: 表示时间点相对于时钟起点的偏移量。

常用的时钟类型:

时钟类型 描述
std::chrono::system_clock 代表系统的挂钟时间(wall clock time)。它的起点是与系统相关的,通常是UNIX纪元(1970年1月1日 00:00:00 UTC)。这个时钟可能会因为系统时间调整(比如,NTP同步)而发生跳跃。
std::chrono::steady_clock 保证是单调递增的,不会因为系统时间调整而发生跳跃。它通常用于测量时间间隔,因为它的值不受外部因素的影响。但是,steady_clock的起点是未定义的,所以不能直接用它来表示一个绝对时间点。
std::chrono::high_resolution_clock 提供系统中最高可能的时间精度。它可能是system_clocksteady_clock的别名,具体取决于系统的实现。使用high_resolution_clock通常是为了获得最精确的时间测量,但是要注意,更高的精度并不总是意味着更高的准确性。有些系统可能只是简单地将system_clock作为high_resolution_clock,并没有提供真正更高精度的时钟。为了确定high_resolution_clock是否是steady_clock,你可以使用std::chrono::is_steady trait:std::chrono::is_steady<std::chrono::high_resolution_clock>::value,如果结果为true,则表示high_resolution_clock是单调的。

获取当前时间点:

#include <iostream>
#include <chrono>

int main() {
    // 获取当前系统时间点
    auto now = std::chrono::system_clock::now();

    // 获取当前稳定时钟时间点
    auto steady_now = std::chrono::steady_clock::now();

    std::cout << "System clock now: " << std::chrono::system_clock::to_time_t(now) << std::endl;
    //steady_clock 的值没有明确的意义,通常用于计算时间差
    //std::cout << "Steady clock now: " << steady_now.time_since_epoch().count() << std::endl; // 这样写会报编译错误, 因为steady_clock的时间起点是未定义的,所以不能直接转换为time_t

    return 0;
}

时间点与时长:

时间点可以加上或减去一个时长,得到一个新的时间点。

#include <iostream>
#include <chrono>

int main() {
    // 获取当前时间点
    auto now = std::chrono::system_clock::now();

    // 计算 5 秒后的时间点
    auto five_seconds_later = now + std::chrono::seconds(5);

    // 计算 10 毫秒前的时间点
    auto ten_milliseconds_earlier = now - std::chrono::milliseconds(10);

    std::cout << "Now: " << std::chrono::system_clock::to_time_t(now) << std::endl;
    std::cout << "Five seconds later: " << std::chrono::system_clock::to_time_t(five_seconds_later) << std::endl;
    std::cout << "Ten milliseconds earlier: " << std::chrono::system_clock::to_time_t(ten_milliseconds_earlier) << std::endl;

    return 0;
}

计算时间间隔:

两个时间点相减,得到一个时长,表示这两个时间点之间的时间间隔。

#include <iostream>
#include <chrono>
#include <thread>

int main() {
    // 记录开始时间点
    auto start = std::chrono::steady_clock::now();

    // 模拟一些耗时操作
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    // 记录结束时间点
    auto end = std::chrono::steady_clock::now();

    // 计算时间间隔
    auto duration = end - start;

    // 将时间间隔转换为毫秒
    auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(duration);

    std::cout << "Elapsed time: " << milliseconds.count() << " milliseconds" << std::endl;

    return 0;
}

system_clocksteady_clock的选择:

  • 如果你需要表示一个绝对时间点(比如,某个事件发生的具体时间),你应该使用system_clock
  • 如果你只需要测量时间间隔(比如,某个操作耗费的时间),你应该使用steady_clock

system_clock::time_point转换为可读的时间字符串:

system_clock::time_point本身并不是一个可以直接阅读的字符串。你需要将其转换为time_t类型,然后再使用localtimestrftime等函数将其格式化为字符串。

#include <iostream>
#include <chrono>
#include <ctime>
#include <iomanip>

int main() {
    auto now = std::chrono::system_clock::now();
    std::time_t now_c = std::chrono::system_clock::to_time_t(now);

    // 使用 localtime 转换为本地时间
    std::tm* now_tm = std::localtime(&now_c);

    // 使用 strftime 格式化时间
    char buf[100];
    std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", now_tm);

    std::cout << "Current time: " << buf << std::endl;

    // C++20 提供了更方便的格式化方式
    // 注意:需要C++20支持
    // std::cout << std::format("{:%Y-%m-%d %H:%M:%S}", now) << std::endl;

    return 0;
}

3. 时钟精度(clock):时间测量的精度

时钟精度指的是时钟能够测量的最小时间单位。不同的时钟有不同的精度。比如,有的时钟精度是秒,有的时钟精度是毫秒,有的时钟精度是纳秒。

std::chrono里,时钟精度由std::chrono::clock_traits来定义。

template <class Clock>
struct clock_traits;

clock_traits 包含以下成员:

  • time_point: 时钟的时间点类型。
  • duration: 时钟的时长类型。
  • period: 时钟的周期(精度)。
  • is_steady: 指示时钟是否稳定的布尔值。

获取时钟精度:

#include <iostream>
#include <chrono>

int main() {
    // 获取 system_clock 的精度
    using system_clock = std::chrono::system_clock;
    using system_clock_period = system_clock::period;

    // system_clock_period::num 是分子, system_clock_period::den 是分母
    std::cout << "system_clock period: " << system_clock_period::num << "/" << system_clock_period::den << " seconds" << std::endl;

    // 获取 high_resolution_clock 的精度
    using high_resolution_clock = std::chrono::high_resolution_clock;
    using high_resolution_clock_period = high_resolution_clock::period;

    std::cout << "high_resolution_clock period: " << high_resolution_clock_period::num << "/" << high_resolution_clock_period::den << " seconds" << std::endl;

    return 0;
}

理解period

period 是一个 std::ratio 类型,表示时钟的精度。 例如,如果 period::num 是 1, period::den 是 1000,那么时钟的精度就是 1/1000 秒,也就是 1 毫秒。

为什么要关心时钟精度?

了解时钟精度可以帮助你选择合适的时钟来测量时间。 如果你需要非常精确的时间测量,你应该选择精度更高的时钟。 但是,精度更高的时钟可能也会带来更高的开销。

高级用法:自定义时长和时钟

std::chrono 允许你自定义时长和时钟,以满足特定的需求。

自定义时长:

#include <iostream>
#include <chrono>

int main() {
    // 定义一个表示 1/100 秒的时长
    using hundredth_of_a_second = std::chrono::duration<long long, std::ratio<1, 100>>;

    // 创建一个表示 5 个 1/100 秒的时长
    hundredth_of_a_second five_hundredths_of_a_second(5);

    std::cout << "Five hundredths of a second: " << five_hundredths_of_a_second.count() << " hundredths of a second" << std::endl;

    // 将其转换为毫秒
    auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(five_hundredths_of_a_second);
    std::cout << "Five hundredths of a second in milliseconds: " << milliseconds.count() << " milliseconds" << std::endl;

    return 0;
}

自定义时钟:

自定义时钟比较复杂,通常情况下你不需要这样做。 但是,如果你需要模拟一个时钟,或者使用一个特殊的硬件时钟,你可以自定义时钟。 自定义时钟需要满足 Clock 的要求,包括定义 time_pointdurationnow() 等成员。

总结:

std::chrono 是一个强大的计时库,可以让你精确地测量时间。 掌握durationtime_pointclock这三个核心概念,你就能在你的程序里灵活地操纵时间。 记住,system_clock 适合表示绝对时间,steady_clock 适合测量时间间隔。 选择合适的时钟和精度,才能得到准确的时间测量结果。

希望今天的讲解对你有所帮助! 以后写代码,就可以精确地测量程序的运行时间,找到性能瓶颈,优化你的代码啦!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注