在 Windows 上正确比较不同版本程序运行速度的方法

· · Windows, Benchmark, Performance, Profiling, Power Management

想在 Windows 上比较程序版本 A 与 B。 这时最不该做的事,就是在同一台机器上各跑一次,然后说「看起来 B 快 8%」。

这 8% 或许真的是代码本身的差异。 但实际上,更可能是电源模式、power plan、散热、后台更新、搜索索引、病毒扫描、亲和性、执行顺序、缓存状态当中的某一项在作怪——这就是 Windows 基准测试常见的现实,相当泥泞的世界。

本文整理在 Windows 上比较不同版本程序运行速度时,如何尽量贴近「代码本身差异」的做法。 主要以 Windows 11 为对象,但 powercfgstart 之类的命令大多在 Windows 10 上同样适用。

先说结论

想提高可复现性,归纳起来主要是以下 6 点:

  1. 先决定「想比较什么」 想看的是代码本身的差异,还是真实用户的体感,需要对齐的环境是不一样的。

  2. 把 power mode 和 power plan 当成两件不同的事来记录 在 Windows 上如果在这里马虎处理,比较很容易变成在比较 OS 的省电策略。

  3. 把「冷启动第一次」与「热机后的稳定状态」分开测量 只有第一次特别快、或者后半段特别慢,都不是罕见的现象。

  4. 按 A→B→A→B 的方式交替执行 如果先把 A 全部跑完再跑 B,散热与后台活动的偏差就会全部压在一边。

  5. 不只看平均值,还要看中位数与离散程度 一个离群值就能扭曲整体判断,平均值比想象中脆弱得多。

  6. 差异较小时,用 ETW / WPR 深挖原因 仅凭体感去争论,大多会变成在雾里互相打架。

先决定你到底要比较什么

「速度比较」听起来是一件事,其实分成两种。

1. 想看代码本身差异的比较

因为算法调整、数据结构变更、编译器优化、运行时更新等原因,想知道实现本身是否变快

这种情况下要尽量削减环境噪声: 使用专用的基准测试 session、固定 power mode、关闭通知、抑制搜索索引与云同步,必要时甚至做 clean boot。

2. 想看真实用户体感的比较

想知道发布之后,用户在日常使用的 Windows 环境中实际感受到的速度。

这种情况下不能把现实中存在的噪声全部消除。 在包含 OneDrive 同步、Defender、通知、普通电源设置等「接近日常」的环境下比较,结果才会更贴近现实。

如果把这两种比较混在一起,结论就会变得拧巴: 「在实验室里快 12%,但现实中几乎没差」「现实中感觉更快,但 CPU 时间却没变」,这类情况很常见。

Windows 上导致结果波动的主要因素

先粗略列出会让结果晃动的因素:

层面 波动因素 典型例子
硬件 CPU / GPU、内存、SSD、散热 笔记本机身薄、有无散热支架
固件 BIOS / UEFI、OEM 控制 省电策略、风扇控制
OS Windows build、驱动、更新状态 同一台电脑更新后行为就变了
电源 AC / DC、power mode、power plan 换成电池供电就像换了个世界
散热 室温、风扇、此前的负载 第一次触发 turbo,后段失速
后台 Update、Defender、同步、通知 运行过程中被扫描或同步打断
调度 优先级、亲和性、NUMA 不同机器的 CPU 分配也不同
数据 / 缓存 OS 缓存、应用缓存 第一次慢,之后才快
构建条件 Debug / Release、PGO、有无日志 本来就是在比较不同的东西

简而言之,「同一台 Windows 机器」如果条件没有对齐,实质上就是两次不同的实验

power mode 与 power plan 要分开考虑

这一点相当关键。

Windows 里存在设置应用中的Power mode,以及传统意义上的Power plan(通过 powercfg 可以看到的电源方案)。 两者外观相似,容易被混为一谈,但草率处理会让比较变得混乱。

在 Windows 的设置应用中,可以从 Settings > System > Power & battery 里选择 Power mode。 Microsoft 的文档中提到,Plugged in / On Battery 各自可以切换 Best power efficiencyBalancedBest performance。而且 Power mode 一旦变化,背后的电源相关设置与 PPM(Processor Power Management)的行为也会随之改变。也就是说,仅仅这里不同,核心停放(core parking)与性能调度策略也可能不同。

另一方面,Power plan 是 BalancedHigh performance 等传统的电源方案。 可以用 powercfg /listpowercfg /getactivescheme 确认。

麻烦的是,Windows 同时存在Power mode 这层覆盖Power plan。 所以基准测试结果至少要记录以下内容:

  • 是接 AC 还是靠电池
  • Power mode 是什么
  • Active power plan 是什么

没有写清楚这 3 项的基准测试结果,事后回看会相当痛苦。

首先应该固定的电源条件

  1. 笔记本电脑务必接 AC 电源再比较 电池供电容易被加上意料之外的限制。

  2. 固定 Power mode 如果是做基准测试,可以先尝试 Best performance

  3. 记录 Active power planpowercfg 把当时的值保留下来。

powercfg /list
powercfg /getactivescheme
  1. 必要时切换到 High performance
# Balanced
powercfg /setactive 381b4222-f694-41f0-9685-ff5bb260df2e

# High performance
powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c

「找不到 High performance」并不罕见

这也是一个容易踩坑的地方。 Microsoft 的文档说明:在支持 Modern Standby 的设备上,只允许使用 Balanced 或由 Balanced 派生出的方案。 所以看到「High performance 不见了,是不是坏了?」的时候,更可能是这台设备在设计上本来就是这样

另外,Microsoft 也提到:如果 Power mode 无法更改,可能是选中了 custom power plan,建议先切换回 Balanced 试试。当 Power mode 的界面无法操作时,这是首先应该怀疑的方向。

压制后台噪声

Windows 是个勤劳的系统,就在你想安静做基准测试的时候,它在后台还在忙很多事。

先重启,然后静置一段时间

修改设置之后先重启一次,登录后不要马上开始测试,等上几分钟。 刚启动时,更新、索引、同步、Defender、各种常驻程序都还在活跃。

认真的比较请用 clean boot

Microsoft 提供了通过 clean boot 将启动项配置到最小化的操作方法: 用 msconfig 停用非 Microsoft 的服务,再用任务管理器停用 Startup apps。

这种方式在降噪上相当有效。 不过它离日常使用环境较远,更适合用在「专门用来比较代码本身差异的实验室式比较」中。

让通知安静下来

Windows 的通知横幅看起来不起眼,实际上相当碍事。 它不仅在视觉上造成干扰,还会影响执行时机、焦点切换以及后台应用的活动。

可以手动开启 Do not disturb,或者至少在基准测试期间关闭通知。

抑制搜索索引与同步

如果基准测试对象会大量读取文件、大量写出生成物、反复重建源码树,搜索索引与云同步就会在暗处悄悄地影响结果。

  • 把基准测试用的目录排除在搜索索引之外
  • 停止 OneDrive / Dropbox / Google Drive 之类的同步
  • 关闭浏览器、Teams、Discord、Slack

这类操作看起来不起眼,但起作用的时候相当明显。

不对齐散热条件的比较,基本上就是在比较散热

CPU 或 GPU 在冷却状态与热机之后,几乎是两种不同的生物。 尤其是笔记本电脑、超薄迷你主机、小型台式机,这种差异更加明显。

需要遵守的规则

  • 尽量对齐室温
  • 固定笔记本电脑的摆放方式
  • 固定 AC 适配器、扩展底座、外接显示器的配置
  • 基准测试前不要进行高负载的操作
  • 把首次执行与稳定状态分开测量

执行顺序要交替

避免先把 A 跑 10 次,再跑 10 次 B。 因为散热、缓存、后台活动的偏差会集中压在其中一边。

推荐以下任意一种顺序:

  • A B A B A B ...
  • A B B A A B B A ...
  • 提前生成随机顺序,再按该顺序执行

测量的指标不同,「快」的含义也会不同

把「快」压缩成一个数字,大多会出问题。 Windows 上常用的代表性指标有以下 3 个。

1. Wall-clock time(实际耗时)

用户实际等待的时间。 最贴近端到端的体感,因此应该首先看这个值。

在 Windows 上,QueryPerformanceCounter(QPC)可用于获取高分辨率的时间。 managed code 一般使用 Stopwatch 系列。 用 DateTime.Now 去看毫秒级数值,多少有点不够严谨。

2. CPU time(用户态 + 内核态时间)

通过 GetProcessTimes 可以获取进程实际占用的 CPU 时间。

这个指标便于观察计算效率。 例如,如果 wall-clock 变快了,但 CPU time 没有变化,可能是缓存、I/O、等待或调度在起作用。

3. Cycle count(CPU 周期数)

通过 QueryProcessCycleTime 可以获取整个进程的 CPU 周期数。

这同样是衡量 CPU 工作量的指标,但和 wall-clock 展示的是不同的侧面。 特别是想确认「等待时间没变,但计算部分是否变轻了」时非常有用。

priority、affinity、NUMA 留到最后再用

这些手段确实有效。 但如果一开始就动它们,反而容易制造出另一种现象。

先用默认状态测量

如果默认状态下就出现差异,这个差异本身就有价值。 一上来就加 /high/affinity,等于引入了「真实 Windows 环境中不会出现的条件」

要用的话,先明确目的

  • /high:不想被其他进程干扰
  • /affinity:固定 CPU 分配以便比较
  • NUMA 控制:在大型机器上连内存局部性也一并对齐

Windows 的 start 命令可以在启动时附加 priority class 与 affinity mask。

start "" /high /wait myapp.exe --bench case1.json
start "" /affinity F /high /wait myapp.exe --bench case1.json

但 /realtime 就不要用了

/realtime 虽然能用,但最好别用。 它往往不是在降噪,而是在制造另一种事故。

推荐的测量流程

把以上内容整理成一套便于实际操作的流程。

偏实验室风格的比较流程

  1. 固定比较对象
    • commit hash / build number
    • compiler / runtime 版本
    • Debug / Release
    • 有无日志、assert、trace
  2. 固定机器条件
    • Windows build
    • BIOS / UEFI 版本
    • 驱动版本
    • 接 AC 电源
    • 室温、摆放方式
  3. 固定电源条件
    • 决定 Power mode
    • 记录 Active power plan
  4. 重启
  5. 测试前等待数分钟
  6. 必要时执行 clean boot
  7. 加入 warm-up
  8. A / B 交替执行
  9. 确保执行次数
  10. 保留中位数、最小值、最大值、p95
  11. 保存 raw data
  12. 差异较小时采集 ETW / WPR

记录下来之后会派上用场的字段

基准测试的 CSV 或 JSON 中,至少应保留以下字段:

timestamp,version,scenario,elapsed_ms,user_ms,kernel_ms,cycles,power_mode,power_plan,ac_or_dc,room_temp_c,notes

如果可能,最好再加上:

cpu_package_temp_start_c,cpu_package_temp_end_c,affinity_mask,priority_class,windows_build,driver_version

基准测试真正重要的,有时不是测量本身,而是之后能否解释清楚

除了平均值,也要看中位数与分布

平均值很方便,但在 Windows 基准测试中很容易被破坏。 只要 Defender 扫描介入一次、弹出一次通知、别的进程碰了一下 SSD,平均值就会被带偏。

推荐组合使用以下几种:

  • 中位数:先看这个
  • p95 / p99:观察尾部是否恶化
  • min / max:观察离群的程度
  • 箱线图或散点图:差异较小时特别有用

出现差异时该怎么解读

把结果组合起来看,会更容易理解。

只有 wall-clock 变快

可能是 I/O、等待时间、缓存或调度方面的改善。

CPU time 和 cycle 都下降了

很可能是实现本身变轻了。

只有第一次慢 / 快

这是 cold / warm 之间的差异,应怀疑启动、初始化、缓存构建、JIT。

执行次数越多越慢

应怀疑散热、降频(throttling)、内存压力、后台活动。

用 ETW / WPR 挖出「为什么更快」

当差异较小、或原因不明确时,转向 Windows 的 ETW(Event Tracing for Windows)系工具是标准做法。

Microsoft 的 Windows Performance Recorder(WPR)是基于 ETW 的记录工具,包含在 Windows ADK 中。 可以一次性采集 CPU、I/O、context switch、page fault 等信息。

最简单的用法大致如下:

wpr -start CPU -filemode

REM 在这里执行基准测试

wpr -stop trace.etl

走到这一步之后,就不再是 「B 好像快了 3%」, 而是能说出 「B 的锁等待减少了,ready time 下降了」 「A 的 file open 增多了,cold start 变慢了」 这种带有明确理由的结论。

总结

在 Windows 上比较不同版本的程序时,真正管用的从来不是什么花哨的偏门技巧。 真正有效的,是下面这些朴素但能提升可复现性的做法:

  • 固定并记录 AC / Power mode / power plan
  • 区分 cold 与 warm
  • A / B 交替执行
  • 观察中位数与分布
  • 必要时执行 clean boot
  • 差异较小时用 ETW / WPR 挖出原因

而最重要的一点是:把「固定了什么、没固定什么」与结果一起写清楚。 基准测试同时也是一份实验条件的记录。

没有写明条件的「提速报告」,顶多算是偶尔猜中的占卜,可复现性相当靠不住。 反过来,只要条件写得扎实,即使差异很小,那个结果也是有价值的。

参考资料

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

常见问题

汇总了咨询这一主题时常见的问题。

在 Windows 上做基准测试时,结果波动的主要原因有哪些?
有很多层面的因素,包括电源模式、power plan、散热、后台更新、搜索索引、病毒扫描、优先级与亲和性、执行顺序、缓存状态等。即使是同一台 Windows 机器,只要这些条件没有对齐,实质上就是两次不同的实验。尤其是笔记本电脑,接 AC 电源还是靠电池运行会让行为出现很大差异,所以务必接 AC 电源进行比较,并把这些条件都记录下来。
power mode 和 power plan 有什么区别?
Power mode 是在设置应用的 Power & battery 中选择的 Best power efficiency / Balanced / Best performance 切换,会影响背后的电源相关设置以及 PPM(Processor Power Management)的行为。Power plan 则是可以用 powercfg 查看的 Balanced、High performance 等传统电源方案。Windows 上这两者同时存在,所以基准测试结果至少要记录 AC 还是电池、Power mode 是什么、以及当前的 Active power plan 这三项。
找不到 High performance 电源方案,是不是设备故障?
很可能不是故障。Microsoft 的文档说明,在支持 Modern Standby 的设备上,只允许使用 Balanced 或由 Balanced 派生出的方案。也就是说,这台设备在设计上就不提供 High performance,这是很正常的情况。另外,如果 Power mode 的界面无法更改,可能是选择了 custom power plan,这时先切换回 Balanced 是比较快的排查方式。
比较版本 A 和 B 的速度时,应该按什么顺序执行?
应避免先把 A 全部跑完再跑 B,因为散热、缓存、后台活动的偏差会全部压在其中一方身上。建议按 A B A B 交替执行,或者提前生成随机顺序再执行。同时应把「冷启动的第一次」与「热机后的稳定状态」分开测量,并且不只看平均值,还要看中位数、p95、最小值和最大值,这样才能避免结果被离群值带偏。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表