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 核心逻辑。你看完应该就能串起来了。
更高级的用法:计算平均耗时
单次测量有噪声。你想想看,系统调度、缓存命中、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