☰
降低微基准测试方差:Google Benchmark 的 CPU 调频检测、锁频与执行环境优化实战
2026/9/25 8:13:42 网站建设 项目流程
  • 性能测试

【免费下载链接】benchmark

A microbenchmark support library

项目地址:https://gitcode.com/gh_mirrors/benchmark5/benchmark
点击查看免费下载

微基准测试的价值取决于结果的可重复性,而一次性的"快慢"往往只是噪声。本文基于 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 }

从源码逻辑可以明确两点检测规则:

  1. 仅逐核检查 Linux 的 CPUFreq 接口:对每个 CPU 读取/sys/devices/system/cpu/cpu<N>/cpufreq/scaling_governor,只要任意一个核的 governor 不是performance,就判定为"调频已启用"并打印警告;
  2. 读取失败则静默:如果这些 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 中,完整的噪声来源清单如下:

  1. 多核机器上各 CPU/核心/线程速率不一致:同一个 benchmark 跑一次和再跑一次,可能因为落在了不同的 CPU 核心上而得到不同结果;
  2. CPU 内部加速特性:如 Intel Turbo Boost、AMD Turbo Core 和 Precision Boost。这类特性即使在 Linux 下使用performancegovernor 时,也可能临时改变 CPU 频率;
  3. 上下文切换与 benchmark 所运行 CPU 上的调度竞争;
  4. Intel Hyperthreading / AMD SMT:超线程核与上面第 3 条造成同类问题(同物理核上的两个逻辑核互相干扰);
  5. 其他 CPU 上运行的代码造成的缓存效应(cache effects);
  6. 非一致性内存架构(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,它要求至少两次非跳过的运行才会产生聚合结果,单次运行不报告聚合数据。聚合指标包括:

指标实现说明
meanStatisticsMean算术平均
medianStatisticsMedian样本数少于 3 时退化为均值;偶数个样本取中位两侧均值,使用nth_element部分排序
stddevStatisticsStdDev样本标准差(n-1 分母),n=1 时返回 0
cvStatisticsCV变异系数 = 标准差 / 均值,样本少于 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 非零值与较短的单次测量时间通常配合使用。这项技术与本文的环境级降噪手段互补:前者从调度时序上打散系统性偏差,后者从硬件状态上消除波动根源,实际工作中可以叠加使用。

七、验证你的降噪效果:一条可操作的检查清单

将上述内容串起来,得到一个可落地的操作序列:

  1. 运行 benchmark,确认输出头部不再出现***WARNING*** CPU scaling is enabled...(若出现,说明仍有核的 governor 不是performance,见第一节检测逻辑);
  2. sudo cpupower frequency-set --governor performance,并用cpupower frequency-info -o proc逐核验证;
  3. echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost关闭 Boost;
  4. 用taskset -c 0 ./mybenchmark绑定核心;条件允许时关闭 SMT;
  5. 加上--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

项目地址:https://gitcode.com/gh_mirrors/benchmark5/benchmark
点击查看免费下载

相关推荐

上一篇:Qwen3-8B-AWQ终极部署指南:如何在单张消费级显卡上运行高性能大语言模型
下一篇:如何快速搭建私有云盘:ZFile在线文件管理系统的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询