- 性能测试
【免费下载链接】benchmark
A microbenchmark support library
微基准测试的价值取决于结果的可重复性,而一次性的"快慢"往往只是噪声。本文基于 docs/reducing_variance.md 展开,讲解 benchmark 库在启动时如何检测并警告 CPU 频率调频(CPU Frequency Scaling),如何关闭调频与处理器加速(Boost),以及如何通过任务亲和、关闭 SMT、收敛工作集等手段系统性地压降单次运行与多次运行之间的方差;并结合src/下的源码说明--benchmark_repetitions重复测量与 mean/median/stddev 统计聚合的实现原理。读完本文,你能够配置一套"低噪声基准测试环境",并正确解读 benchmark 输出中由重复测量产生的聚合统计结果。
一、从警告信息说起:benchmark 如何检测 CPU 调频
当你运行一个 benchmark 程序时,如果看到如下输出:
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.这表示系统启用了 CPU 频率调频。在调频状态下,CPU 会随负载动态升频/降频,实测耗时(real time measurement)因此变得"嘈杂"(noisy),且调频机制本身还会引入额外开销。此时建议关闭 CPU 调频,并考虑其他稳定系统性能的手段。
这条警告并非装饰性提示,而是由 benchmark 库在初始化 Reporter 上下文时实际探测内核状态后打印的。在 src/reporter.cc 中:
if (CPUInfo::Scaling::ENABLED == info.scaling) { Out << "***WARNING*** CPU scaling is enabled, the benchmark " "real time measurements may be noisy and will incur extra " "overhead.\n"; }而info.scaling的取值来自 src/sysinfo.cc 中的CpuScaling()函数:
CPUInfo::Scaling CpuScaling(int num_cpus) { // We don't have a valid CPU count, so don't even bother. if (num_cpus <= 0) return CPUInfo::Scaling::UNKNOWN; #if defined(BENCHMARK_OS_QNX) return CPUInfo::Scaling::UNKNOWN; #elif !defined(BENCHMARK_OS_WINDOWS) // On Linux, the CPUfreq subsystem exposes CPU information as files on the // local file system. ... std::string res; for (int cpu = 0; cpu < num_cpus; ++cpu) { std::string governor_file = StrCat("/sys/devices/system/cpu/cpu", cpu, "/cpufreq/scaling_governor"); if (ReadFromFile(governor_file, &res) && res != "performance") return CPUInfo::Scaling::ENABLED; } return CPUInfo::Scaling::DISABLED; #else return CPUInfo::Scaling::UNKNOWN; #endif }从源码逻辑可以明确两点检测规则:
- 仅逐核检查 Linux 的 CPUFreq 接口:对每个 CPU 读取
/sys/devices/system/cpu/cpu<N>/cpufreq/scaling_governor,只要任意一个核的 governor 不是performance,就判定为"调频已启用"并打印警告; - 读取失败则静默:如果这些 sysfs 文件不可读(例如非 Linux 系统、未启用 CPUFreq),函数返回
UNKNOWN,不会误报警告。这也解释了为什么在部分平台上你永远看不到这条警告。
同一个scaling状态还会影响频率的读取策略:在 src/sysinfo.cc 的GetCPUCyclesPerSecond()中,当 scaling 为DISABLED时优先读取scaling_cur_freq(当前频率),否则倾向于使用更高的标称频率,避免把"降频时读到的低频"当成 CPU 的真实频率。
二、关闭 CPU 调频:使用 cpupower 切换到 performance 模式
如何具体关闭调频,取决于 Linux 发行版、桌面环境和已安装的程序,细节因系统而异。文档给出了一个简单而通用的方案:使用与内核一同维护、由发行版提供的cpupower工具,把处理器性能调节器(governor)改为performance。
步骤 1:以 root 身份设置 performance governor
sudo cpupower frequency-set --governor performance步骤 2:验证所有 CPU 均已使用 performance governor
cpupower frequency-info -o proc执行上述设置后,再运行 benchmark,单次测量之间的抖动会明显减少——因为每个核都锁定在标称最高频率上运行,不再随负载波动。这也正是上面CpuScaling()判定scaling == DISABLED的前提条件。
三、调频之外:基准测试噪声的六大来源
CPU 调频只是噪声来源之一。在 docs/reducing_variance.md 中,完整的噪声来源清单如下:
- 多核机器上各 CPU/核心/线程速率不一致:同一个 benchmark 跑一次和再跑一次,可能因为落在了不同的 CPU 核心上而得到不同结果;
- CPU 内部加速特性:如 Intel Turbo Boost、AMD Turbo Core 和 Precision Boost。这类特性即使在 Linux 下使用
performancegovernor 时,也可能临时改变 CPU 频率; - 上下文切换与 benchmark 所运行 CPU 上的调度竞争;
- Intel Hyperthreading / AMD SMT:超线程核与上面第 3 条造成同类问题(同物理核上的两个逻辑核互相干扰);
- 其他 CPU 上运行的代码造成的缓存效应(cache effects);
- 非一致性内存架构(NUMA):跨 NUMA 节点访存延迟不同。
这些噪声既可能体现在单次运行内部(即通过--benchmark_repetitions=N进行的重复测量之间),也可能体现在多次独立运行整个 benchmark 程序之间。
由于方差来源与操作系统和硬件架构强相关,这也是许多公司维护专门用于性能测试的专用机器(dedicated machines)的原因之一。
四、降低方差的实操手段(典型 Linux 工作站)
原文档给出了典型 Linux 工作站上较易操作且效果明显的若干手段,逐条说明如下。
4.1 使用 performance governor
如第二节所述,用cpupower把 governor 设为performance。这是所有手段的基础。
4.2 关闭处理器加速(Boost)
即使 governor 是performance,Turbo Boost 之类的加速仍会临时超频。关闭方式:
echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost该 sysfs 节点的作用可参考 Linux 内核文档Documentation/cpu-freq/boost.txt的说明。
4.3 用 taskset 固定 CPU 亲和性
将 benchmark 进程绑定到固定 CPU,消除"落在哪个核上"的随机性:
taskset -c 0 ./mybenchmark绑定单核后,第 1、2、3 类噪声(核心速率差异、调度竞争、上下文切换)都会被显著削弱。
4.4 关闭 Hyperthreading/SMT
可以在 BIOS 中关闭,也可以通过/sys文件系统操作(LLVM 项目在其 Benchmarking 文档中给出了基于 sysfs 的具体做法)。关闭 SMT 直接消除第 4 类噪声,避免同物理核上的"兄弟逻辑核"干扰。
4.5 关闭其他"基于定时器做事"的程序
关闭浏览器、桌面环境等会基于定时器执行非平凡工作的程序,减少第 3、5 类噪声(调度竞争与缓存干扰)。
4.6 把工作集收敛到 L1 缓存内
缩小 benchmark 的工作集(working set)使其能放进 L1 缓存,可以消除缓存层级带来的波动;但文档特别提醒:这种做法可能让你在不真实(unrealistic)的场景上过度优化,需要权衡取舍。
五、库内机制:--benchmark_repetitions 与统计聚合
压低环境噪声之外,benchmark 库本身还提供"用多次测量 + 统计聚合"来对抗残余方差的第一道防线。
命令行参数:--benchmark_repetitions=<num_repetitions>,默认值为 1,定义见 src/benchmark.cc:
BM_DEFINE_int32(benchmark_repetitions, 1);每个 benchmark 也可以单独通过.Repetitions(n)设置;在 src/benchmark_runner.cc 中,实例级设置优先于全局 flag:
repeats(b.repetitions() != 0 ? b.repetitions() : FLAGS_benchmark_repetitions),重复测量的执行流程:BenchmarkRunner::DoOneRepetition()(src/benchmark_runner.cc)中有一个关键设计——迭代次数只在第一次重复时通过PredictNumItersNeeded()逐轮放大估算得到;之后的重复直接复用第一次计算出的迭代次数,保证各次重复在相同的测量长度下比较。所有重复完成后,GetResults()调用ComputeStats()对这一组报告做统计聚合(src/benchmark_runner.cc):
// Calculate additional statistics over the repetitions of this instance. run_results.aggregates_only = ComputeStats(run_results.non_aggregates);统计实现:ComputeStats()位于 src/statistics.cc,它要求至少两次非跳过的运行才会产生聚合结果,单次运行不报告聚合数据。聚合指标包括:
| 指标 | 实现 | 说明 |
|---|---|---|
| mean | StatisticsMean | 算术平均 |
| median | StatisticsMedian | 样本数少于 3 时退化为均值;偶数个样本取中位两侧均值,使用nth_element部分排序 |
| stddev | StatisticsStdDev | 样本标准差(n-1 分母),n=1 时返回 0 |
| cv | StatisticsCV | 变异系数 = 标准差 / 均值,样本少于 2 个或均值为 0 时返回 0 |
可以推断,cv(变异系数)正是用来直观衡量"重复测量之间的相对离散程度"的:如果你的 repetitions 结果 cv 明显偏高,说明环境噪声尚未压干净,应回到第四节逐项排查,而不是急于解读 mean 的绝对值。
六、进阶:随机交错(Random Interleaving)
仓库中另有一篇文档 docs/random_interleaving.md 专门介绍针对"运行间方差"(run-to-run variance)的随机交错技术:把同一微基准的多次重复与其他微基准的重复随机交错执行,据该文档引用的上游数据,平均可降低约 40% 的运行间方差。使用方式:
--benchmark_enable_random_interleaving=true \ --benchmark_repetitions=9 \ --benchmark_min_time=0.1其中 repetitions 非零值与较短的单次测量时间通常配合使用。这项技术与本文的环境级降噪手段互补:前者从调度时序上打散系统性偏差,后者从硬件状态上消除波动根源,实际工作中可以叠加使用。
七、验证你的降噪效果:一条可操作的检查清单
将上述内容串起来,得到一个可落地的操作序列:
- 运行 benchmark,确认输出头部不再出现
***WARNING*** CPU scaling is enabled...(若出现,说明仍有核的 governor 不是performance,见第一节检测逻辑); sudo cpupower frequency-set --governor performance,并用cpupower frequency-info -o proc逐核验证;echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost关闭 Boost;- 用
taskset -c 0 ./mybenchmark绑定核心;条件允许时关闭 SMT; - 加上
--benchmark_repetitions=N(N ≥ 3 使 median/stddev 有意义),观察聚合输出中的 mean/median/stddev/cv,以 cv 判断方差是否收敛。
八、延伸阅读
原文档末尾给出了两个主题相关的进一步学习方向(此处按名称列出,见原文档获取入口):
- LLVM 项目的 Benchmarking 文档(其中包含通过
/sys文件系统关闭 SMT 的具体做法); - Arch Wiki 的 CPU frequency scaling 页面。
这两份资料与本仓库的实现互相印证:benchmark 库源码中对/sys/devices/system/cpu/cpu*/cpufreq/路径的直接依赖(见 src/sysinfo.cc)说明,理解 Linux CPUFreq 的 sysfs 接口是排查基准测试方差问题的基本功。
适用前提小结:本文涉及的 sysfs 路径(scaling_governor、cpufreq/boost)与cpupower均为 Linux 平台特性;从 src/sysinfo.cc 的编译分支可以看出,调频检测逻辑本身仅对非 Windows/非 QNX 的类 Linux 平台生效,其他平台上scaling为UNKNOWN,不会出现该警告。跨平台对比测试时,请额外注意各平台可用的降噪手段不同。
- 性能测试
【免费下载链接】benchmark
A microbenchmark support library
相关推荐
Google Benchmark 降低基准测试方差完整指南:CPU 调频、ASLR 与噪声控制实践
Google Benchmark 降低基准测试方差完整指南:CPU 调频、ASLR 与噪声控制实践 本指南以 Google Benchmark( GitHub_
性能测试开发工具Google Benchmark 随机交错(Random Interleaving):用随机化执行顺序压降低重复测量方差
Google Benchmark 随机交错(Random Interleaving):用随机化执行顺序压降低重复测量方差 本指南以 benchmark 仓库官方
性能测试开发工具google/benchmark 随机交错(Random Interleaving)详解:降低微基准方差的原理、用法与源码实现
google/benchmark 随机交错(Random Interleaving)详解:降低微基准方差的原理、用法与源码实现 本文围绕 Google Benc
性能测试
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考