在 Windows 上正确比较不同版本程序运行速度的方法
· 小村 豪 · Windows, Benchmark, Performance, Profiling, Power Management
想在 Windows 上比较程序版本 A 与 B。 这时最不该做的事,就是在同一台机器上各跑一次,然后说「看起来 B 快 8%」。
这 8% 或许真的是代码本身的差异。 但实际上,更可能是电源模式、power plan、散热、后台更新、搜索索引、病毒扫描、亲和性、执行顺序、缓存状态当中的某一项在作怪——这就是 Windows 基准测试常见的现实,相当泥泞的世界。
本文整理在 Windows 上比较不同版本程序运行速度时,如何尽量贴近「代码本身差异」的做法。
主要以 Windows 11 为对象,但 powercfg、start 之类的命令大多在 Windows 10 上同样适用。
先说结论
想提高可复现性,归纳起来主要是以下 6 点:
-
先决定「想比较什么」 想看的是代码本身的差异,还是真实用户的体感,需要对齐的环境是不一样的。
-
把 power mode 和 power plan 当成两件不同的事来记录 在 Windows 上如果在这里马虎处理,比较很容易变成在比较 OS 的省电策略。
-
把「冷启动第一次」与「热机后的稳定状态」分开测量 只有第一次特别快、或者后半段特别慢,都不是罕见的现象。
-
按 A→B→A→B 的方式交替执行 如果先把 A 全部跑完再跑 B,散热与后台活动的偏差就会全部压在一边。
-
不只看平均值,还要看中位数与离散程度 一个离群值就能扭曲整体判断,平均值比想象中脆弱得多。
-
差异较小时,用 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 efficiency、Balanced、Best performance。而且 Power mode 一旦变化,背后的电源相关设置与 PPM(Processor Power Management)的行为也会随之改变。也就是说,仅仅这里不同,核心停放(core parking)与性能调度策略也可能不同。
另一方面,Power plan 是 Balanced、High performance 等传统的电源方案。
可以用 powercfg /list 或 powercfg /getactivescheme 确认。
麻烦的是,Windows 同时存在Power mode 这层覆盖和Power plan。 所以基准测试结果至少要记录以下内容:
- 是接 AC 还是靠电池
- Power mode 是什么
- Active power plan 是什么
没有写清楚这 3 项的基准测试结果,事后回看会相当痛苦。
首先应该固定的电源条件
-
笔记本电脑务必接 AC 电源再比较 电池供电容易被加上意料之外的限制。
-
固定 Power mode 如果是做基准测试,可以先尝试
Best performance。 -
记录 Active power plan 用
powercfg把当时的值保留下来。
powercfg /list
powercfg /getactivescheme
- 必要时切换到 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 虽然能用,但最好别用。
它往往不是在降噪,而是在制造另一种事故。
推荐的测量流程
把以上内容整理成一套便于实际操作的流程。
偏实验室风格的比较流程
- 固定比较对象
- commit hash / build number
- compiler / runtime 版本
- Debug / Release
- 有无日志、assert、trace
- 固定机器条件
- Windows build
- BIOS / UEFI 版本
- 驱动版本
- 接 AC 电源
- 室温、摆放方式
- 固定电源条件
- 决定 Power mode
- 记录 Active power plan
- 重启
- 测试前等待数分钟
- 必要时执行 clean boot
- 加入 warm-up
- A / B 交替执行
- 确保执行次数
- 保留中位数、最小值、最大值、p95
- 保存 raw data
- 差异较小时采集 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 挖出原因
而最重要的一点是:把「固定了什么、没固定什么」与结果一起写清楚。 基准测试同时也是一份实验条件的记录。
没有写明条件的「提速报告」,顶多算是偶尔猜中的占卜,可复现性相当靠不住。 反过来,只要条件写得扎实,即使差异很小,那个结果也是有价值的。
参考资料
- Microsoft Support: Change the power mode for your Windows PC
- Microsoft Learn: Power Policy Settings
- Microsoft Learn: Customize the Windows performance power slider
- Microsoft Learn: Powercfg command-line options
- Microsoft Support: How to perform a clean boot in Windows
- Microsoft Support: Notifications and Do Not Disturb in Windows
- Microsoft Support: Search indexing in Windows
- Microsoft Learn: Configure custom exclusions for Microsoft Defender Antivirus
- Microsoft Support: Device Security in the Windows Security App
- Microsoft Learn: QueryPerformanceCounter function
- Microsoft Learn: Acquiring high-resolution time stamps
- Microsoft Learn: GetProcessTimes function
- Microsoft Learn: QueryProcessCycleTime function
- Microsoft Learn: start command
- Microsoft Learn: SetPriorityClass function
- Microsoft Learn: SetProcessAffinityMask function
- Microsoft Learn: Processor Groups
- Microsoft Learn: Windows Performance Recorder
- Microsoft Learn: WPR Command-Line Options
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
如何公平比较 C#/C++/Java/Go 的运行速度
整理如何公平比较 C#、C++、Java、Go 的运行速度,涵盖测量设计、warm-up、环境固定、统计数据的解读方式,以及具体的基准测试项目。
如何理解 Windows 的会话隔离 ── Session 0・RDP・多用户同时运行
整理 Windows 应用程序开发者容易混淆的「会话」概念。说明服务无法显示 UI 的 Session 0 隔离原因、RDP 连接时会话的行为、命名对象的会话隔离,以及共享 PC・RDS 环境中常见的设计失误,从实务角度解说。
防止 Windows 应用程序重复启动 ── 命名 Mutex 与重复执行时的窗口前置
整理业务型 Windows 应用程序的常见需求——「不让同一个应用程序启动两次」——如何用命名 Mutex 来实现。涵盖 Global\ 与 Local\ 命名空间差异在 RDP 环境中的陷阱、拥有线程限制与 AbandonedMutexException、在 SetFor...
在 WinForms/WPF 应用中集成 Entra ID 认证 —— MSAL.NET 与 WAM Broker 的实务架构
本文以实务视角整理在 WinForms/WPF 桌面应用中集成 Entra ID(原 Azure AD)认证的步骤:公共客户端的思路、ROPC 被弃用的现状、应用注册、MSAL.NET 的 AcquireTokenSilent 模式、WAM Broker、令牌缓存的持久化,...
业务应用的日期时间与时区 ── 从 DateTime 的陷阱到 UTC 存储原则、测试设计
服务器迁移后时间整整差了 9 个小时、只有海外分支机构的日期会变成前一天——本文从 DateTime 的 Kind 与隐式转换梳理日期时间事故的根本原因。内容涵盖与 DateTimeOffset 的取舍、UTC 存储与 ISO 8601 原则、TimeZoneInfo 与夏...
常见问题
汇总了咨询这一主题时常见的问题。
- 在 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 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。