用 std::chrono 手写基准测试最稳妥,需循环多次取中位数或最小值、禁用优化、预分配内存、固定 cpu 频率与核心,并验证内联与汇编。

用 std::chrono 手写基准测试最稳妥
自己控制测量逻辑,避免框架引入的调度、缓存、预热偏差。C++ 标准库的 std::chrono 足够精确(纳秒级),且无额外依赖。
常见错误是只测单次运行时间——受 CPU 频率波动、TLB miss、分支预测失败等干扰极大,结果不可信。
- 必须循环多次(比如 1000–10000 次),取中位数或多次测量的最小值(更贴近“理想路径”耗时)
- 每次循环前加
_mm_pause()或std::this_thread::yield()降低调度干扰(非必需但推荐) - 用
std::chrono::high_resolution_clock::now(),别用system_clock(它可能被系统时间调整拖累) - 确保被测函数不被编译器优化掉:对返回值做 volatile 强制读取,或用
do_not_optimize_away()类似手法
google/benchmark 适合中大型项目但得小心配置
它自动处理预热、迭代次数、统计(如均值、标准差)、输出格式,但默认行为容易误导人。
典型问题:没关 CPU 频率缩放(scaling_governor=performance),或没绑核(taskset -c 0 ./bench),导致数据抖动超 20%。
立即学习“C++免费学习笔记(深入)”;
- 务必启用
BENCHMARK_MAIN()并用BENCHMARK(BM_Func)->Repetitions(3)->ReportAggregatesOnly(true)控制重复与聚合方式 - 禁用编译器对 benchmark 函数的 LTO 和 whole-program 优化(加
-fno-lto -fno-semantic-interposition) - Linux 下运行前执行:
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor - 注意
State::PauseTiming()和State::ResumeTiming()—— 初始化开销必须剔除,否则测的是“构造+计算”而非纯计算
别信 Release 模式下没内联的函数耗时
很多“慢函数”在实际调用链里根本不会单独存在——它们会被 inline 进上层,消除调用开销和寄存器保存成本。手写 benchmark 如果强制不内联([[gnu::noinline]]),测出来的是下界失真值。
真实性能看的是上下文:是否在 hot loop 里?参数是否常量?是否触发 vectorization?
- 想验证内联效果,对比加
[[gnu::always_inline]]和不加的 benchmark 结果 - 用
objdump -d或 Compiler Explorer 看汇编,确认关键路径是否真的 inlined - 对模板函数,benchmark 必须用具体实例化类型(如
BM_VectorAdd<int></int>),否则编译器可能生成泛型未优化版本
内存分配是最大隐藏变量
哪怕只测一个 std::vector::push_back,如果没预分配(reserve),就混入了 malloc、页表更新、TLB 填充等非目标开销。
更隐蔽的是:不同 benchmark 轮次之间,堆内存碎片程度不同,new 耗时可能差 3 倍。
- 所有涉及堆分配的测试,必须在
State::StartKeepRunning()外预分配好缓冲区,循环内只操作已分配内存 - 用
std::pmr::monotonic_buffer_resource替代默认分配器,能大幅压平方差 - 怀疑是缓存效应?加
__builtin_ia32_clflushopt刷特定内存地址,或用posix_memalign分配独占 cache line 的 buffer
真正难的不是跑出一个数字,而是让两次 benchmark 的硬件状态、内存布局、指令流水线深度尽可能一致。哪怕同一台机器,隔五分钟再跑,结果都可能漂移——这不是工具的问题,是现代 CPU 本身就不打算给你确定性。











