如何使用c++的std::chrono进行高精度性能测量? (替代C风格time)

std::chrono::high_resolution_clock 并非总是纳秒级,实际精度需运行时检查分母;应测代码块而非单次调用,避免在循环内频繁调用 now() 以防止测量污染。

std::chrono::high_resolution_clock 真的够高吗?

多数场景下,std::chrono::high_resolution_clock 是最接近硬件精度的时钟,但它在不同平台语义不同:Windows 上通常映射到 QueryPerformanceCounter(纳秒级),Linux 上常为 CLOCK_MONOTONIC(微秒或纳秒,取决于内核配置)。不能假设它一定返回纳秒——实际精度需运行时检查:

auto res = std::chrono::high_resolution_clock::period::den; // 分母,如 1000000000 表示纳秒
若结果是 1000000,说明底层只支持微秒。更稳妥的做法是统一用 std::chrono::nanoseconds 存储差值,让系统自动做精度截断或舍入。

如何避免测量误差:别在循环里反复调用 now()

频繁调用 now() 本身有开销,尤其在 tight loop 中会污染测量结果。正确做法是「测一段代码块」,而非「测单次函数调用」:

  • auto start = std::chrono::high_resolution_clock::now(); 记录起点
  • 执行待测逻辑(确保编译器不优化掉,必要时加 volatile 或用结果)
  • 再调用一次 now() 得终点,相减得 duration
  • 避免把 now() 放进被测循环内部——那测的是时钟开销,不是你的算法

例如,错误写法:

for (int i = 0; i < N; ++i) {
    auto t = std::chrono::high_resolution_clock::now(); // ❌ 每次都调用
    do_work();
}
正确写法:
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; ++i) {
    do_work();
}
auto end = std::chrono::high_resolution_clock::now(); // ✅ 只调两次

duration_cast 的陷阱:向下取整 vs 四舍五入

std::chrono::duration_cast 默认向零截断(即向下取整),比如 1234 ns 转成 std::chrono::microseconds 得到 1 μs,不是 1.234。若需要更准的浮点表示,应先转为 double 基础单位:

  • 要整数微秒:用 duration_cast<:chrono::microseconds>(end - start).count()
  • 要带小数的毫秒:用 std::chrono::duration(end - start).count()
  • 避免链式 cast:duration_cast(duration_cast(d)) 会多一次截断,直接 cast 到目标类型即可

常见误用:

auto d = end - start;
auto us = std::chrono::duration_cast(d).count(); // 截断后整数
auto us_f = std::chrono::duration(d).count(); // 保留小数,如 1234.567

跨线程 / 多核时要注意 clock 稳定性

high_resolution_clock 在某些老 CPU 或 BIOS 设置下可能受频率缩放影响(如 Intel SpeedStep),导致同一段代码在不同核心上测出差异较大的时间。若需严格可重现的性能数据:

  • 优先用 std::chrono::steady_clock(单调、不受系统时间调整影响,但精度可能略低)
  • Linux 下可绑定进程到固定 CPU 核并禁用节能:taskset -c 0 ./bench && echo performance | sudo tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
  • Windows 上建议关闭「快速启动」和「连接待机」,并在电源选项中设为「高性能」

真正难搞的不是写对代码,而是让硬件别偷偷降频——很多“性能波动”问题根源在此。