15、C++11 实用工具:std::chrono时间库,精确测量代码性能。

做C++开发这么多年,我遇到过不少“性能玄学”。

明明算法看起来没问题,跑起来就是慢半拍。或者优化了一通,自我感觉良好,结果一测——更慢了。

这时候,你需要一把精确的尺子。std::chrono 就是C++11给我们的这把尺子。它不仅能测时间,还能帮你定位瓶颈。今天我们就把它聊透。

为什么不用 clock() 或 gettimeofday()?

在C++11之前,测量时间是个麻烦事。

  • clock():返回的是CPU时间,不是墙上时间。多线程下基本没用。
  • gettimeofday():POSIX专用,Windows上没法用。
  • QueryPerformanceCounter:Windows专属,跨平台得写一堆宏。

说白了,每个平台都有自己的“方言”。std::chrono 就是C++标准委员会给咱们的统一翻译官。一次编写,到处运行。

核心三件套:时钟、时长、时间点

std::chrono 其实就三个核心概念。搞懂了它们,你就掌握了90%。

概念 说明 常用类型
时钟 (Clock) 提供当前时间点的“源头” system_clock, steady_clock, high_resolution_clock
时长 (Duration) 一段时间间隔,比如3秒、5毫秒 seconds, milliseconds, microseconds, nanoseconds
时间点 (Time Point) 某个时钟下的具体时刻 time_point<Clock>

我个人习惯把“时间点”想象成照片,“时长”想象成视频片段。照片记录瞬间,视频记录过程。

实战:测量一段代码的执行时间

这是最常用的场景。直接上代码:

#include <iostream>
#include <chrono>
#include <thread> // 模拟耗时操作

int main() {
    // 1. 获取开始时间点
    auto start = std::chrono::steady_clock::now();

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

    // 2. 获取结束时间点
    auto end = std::chrono::steady_clock::now();

    // 3. 计算时长
    auto duration = end - start;

    // 4. 转换为毫秒输出
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
    std::cout << "耗时: " << ms << " 毫秒" << std::endl;

    return 0;
}

嗯,这里要注意:为什么用 steady_clock 而不是 system_clock?

system_clock 是挂钟时间。如果用户改了系统时间,或者NTP同步了,你的测量结果会突然跳变。steady_clock 是单调递增的,不受系统时间影响。测量性能时,请始终用 steady_clock。

核心原则:测量性能用 steady_clock,获取日历时间用 system_clock。

封装一个通用的计时器

每次写 start/end 太啰嗦了。我在项目中习惯封装一个简单的 ScopedTimer:

#include <iostream>
#include <chrono>
#include <string>

class ScopedTimer {
public:
    ScopedTimer(const std::string& name) 
        : m_name(name), m_start(std::chrono::steady_clock::now()) {}

    ~ScopedTimer() {
        auto end = std::chrono::steady_clock::now();
        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - m_start).count();
        std::cout << "[" << m_name << "] 耗时: " << ms << " ms" << std::endl;
    }

private:
    std::string m_name;
    std::chrono::steady_clock::time_point m_start;
};

// 使用示例
void someFunction() {
    ScopedTimer timer("someFunction");
    // ... 你的代码
}

这个技巧我用了好几年。RAII 思想,进入作用域开始计时,离开作用域自动打印。干净利落。

避坑指南:高精度时钟的陷阱

我曾经在优化一个实时音频处理模块时,用了 high_resolution_clock。结果发现不同平台下,它的行为完全不一样。

有的平台上 high_resolution_clock 就是 steady_clock 的别名。有的平台上它却是 system_clock 的别名。甚至有的编译器里,它的精度还不如 steady_clock。

警告:不要迷信 high_resolution_clock。C++标准没有强制要求它提供最高精度。如果你需要单调递增且稳定的时钟,直接用 steady_clock 就好。

知识体系:一张图看懂 chrono

下面这张图是我梳理的 chrono 核心逻辑。你看完应该就能串起来了。

std::chrono 核心知识体系 时钟 (Clock) steady_clock / system_clock 时长 (Duration) seconds / milliseconds / ... 时间点 (Time Point) time_point<Clock> 时钟提供时间点,时间点相减得到时长 auto duration = end_time - start_time; duration_cast<milliseconds>(dur).count() 得到精确的数值(毫秒/微秒/纳秒)

更高级的用法:计算平均耗时

单次测量有噪声。你想想看,系统调度、缓存命中、CPU降频,都会影响结果。我一般会跑多次取平均:

#include <iostream>
#include <chrono>
#include <vector>

template <typename Func>
double measure_avg(Func func, int iterations = 10) {
    std::vector<long long> samples;
    samples.reserve(iterations);

    for (int i = 0; i < iterations; ++i) {
        auto start = std::chrono::steady_clock::now();
        func();
        auto end = std::chrono::steady_clock::now();
        auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
        samples.push_back(ns);
    }

    // 去掉最大值和最小值,减少噪声
    std::sort(samples.begin(), samples.end());
    long long sum = 0;
    for (int i = 1; i < iterations - 1; ++i) {
        sum += samples[i];
    }
    return static_cast<double>(sum) / (iterations - 2);
}

// 使用
void test_function() {
    volatile int x = 0;
    for (int i = 0; i < 1000; ++i) x += i;
}

int main() {
    double avg_ns = measure_avg(test_function, 20);
    std::cout << "平均耗时: " << avg_ns << " 纳秒" << std::endl;
    return 0;
}

小技巧:测量极短的操作(比如几十纳秒)时,记得把函数调用本身的开销也考虑进去。可以测一个空函数作为基线,然后减去它。

总结一下

std::chrono 不是什么黑魔法。它就是一套类型安全、跨平台的时间工具。记住三点:

  • 测量性能用 steady_clock
  • 获取日历时间用 system_clock
  • duration_cast 在不同时间单位间转换

我在做性能调优时,chrono 几乎每天都要用。它就像一把游标卡尺,帮你把代码的每一丝延迟都量出来。没有它,优化就是盲人摸象。


公众号:蓝海资料掘金营,微信deep3321