更新记录(仅首版,2026年08月21日 发布)
- 首次发布
引用本文(DOI(已登记存档): 10.5281/zenodo.22176583)
以下 DOI 指向先前登记的存档,内容可能与当前正文不同。引用当前正文时,请使用本页网址。
Go Komura(2026)。《WPR/WPA 实战——从系统整体排查“整台 PC 变慢”的性能调查入门》。小村软件有限公司。 https://comcomponent.com/zh-CN/blog/wpr-wpa-system-performance-analysis/
- DOI(已登记存档)
- 10.5281/zenodo.22176583
- DOI(上次登记版本)
- 10.5281/zenodo.22176584
“装了某个应用之后整台 PC 就变慢了。可是任务管理器里 CPU 和内存都还有余量。”“只有一台开机要花 3 分钟。”——这类咨询最棘手的地方在于,连该查哪个进程都不知道。
即使表现为应用 A 慢,原因也可能是杀毒软件的扫描、另一个服务的大量写入,或者跨多个进程的锁连锁。需要的是把整个 OS 的活动记录在同一条时间轴上,追查时间到底消耗在了哪里。
为此准备的工具,就是 Windows Performance Recorder(WPR)和 Windows Performance Analyzer(WPA)。WPR 使用 ETW(Event Tracing for Windows)采集记录,WPA 则用图表和表格阅读这份记录。占用了 CPU 的进程和调用栈、线程当时在等待的对象、磁盘 I/O 的发起方和目标文件,都可以按时刻顺序查看。
本文要回答的是,“整台 PC 的慢该怎么记录,又该从 CPU、等待时间、I/O 中的哪一处查起”。面向中小企业的信息系统负责人和 Windows 应用开发者,依据 2026 年 8 月时点的第一手资料,说明从采集实务到分析的全过程。阅读时请特别以“CPU 高的情况”和“CPU 低却很慢的情况”之间的差别为主线。
与排查文件和注册表访问的 Process Monitor、追查 .NET 应用 CPU 与 GC 的 PerfView 之间怎么分工,在第 2 章整理。
1. 先说结论
基本流程是“用 WPR 采集 → 在 WPA 中缩小到出问题的时间段 → 区分是 CPU、等待还是 I/O”。 只盯着某个进程看不出来的问题,把所有进程和内核放在同一条时间轴上追踪就能查清。12
把采集的地方和阅读的地方分开
采集用的 wpr.exe 从 Windows 8.1 起就随操作系统内置,无需额外安装。GUI 版的 WPRUI 和分析用的 WPA 包含在 Windows ADK 中。由于可以做成“在客户环境只用 wpr.exe 采集,阅读交给自己机器上的 WPA”这样的分工,即使是不能加装软件的服务器也能完成采集。12
如果问题可以复现,以管理员权限执行 wpr -start GeneralProfile -filemode → 复现问题 → wpr -stop C:\temp\trace.etl 就是基本的三步。3 请先准备好保存目录、取得采集的批准、定好 ETL 的处理方式,然后再执行。 守候型的问题和启动过程中的问题另有采集方法,请查看第 3 章和第 8 章。
在 WPA 中最先打开的图表
| 该区间的状态 | 最先查看的内容 | 需要确认的事情 |
|---|---|---|
| CPU 很高 | 第 5 章:CPU Usage (Sampled) | 哪个进程、哪个调用栈、哪个函数用掉了 CPU |
| CPU 整体不高,但某个核心、某条线程被跑满 | 第 5 章:CPU Usage (Sampled) | 是否存在被整体使用率掩盖的 CPU 瓶颈 |
| CPU 整体和单个核心都没有被跑满却很慢 | 第 6 章:CPU Usage (Precise) | 在哪里等待、等了多久、是谁解除了等待 |
| 怀疑磁盘或文件访问 | 第 7 章:Disk Usage / File I/O | 排队时间与设备的处理时间,以及 I/O 的发起方和目标文件 |
Sampled 是用大约每 1 毫秒一次的采样来看 CPU 时间构成的。6 Precise 则从上下文切换的完整记录中追查等待。以 Waits → ReadyingProcess → ReadyThreadStack 为线索,一路追出是谁解除了等待,是本文最想传达的技术。47
按目的选择阅读路线
负责采集的读者从第 2 章和第 3 章开始,要阅读已采集 ETL 的读者从第 4 章开始。要按函数名阅读调用栈就需要配置符号,自研应用还要添加 PDB 的路径。8 如果要排查 .NET 的 JIT 代码,采集时的 CLR 事件也不可少,所以请在采集前先看第 4 章中面向 .NET 的说明。
整个排查的推进方式和 ETL 的处理方式汇总在第 9 章。ETL 中包含进程名、文件路径等内部信息,因此采集要控制在必要的最小范围,交到公司外部的条件也要事先定好。
图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 16 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle
2. 工具的定位——采集靠 WPR,阅读靠 WPA
Windows Performance Toolkit(WPT)是包含在 Windows ADK(Windows Assessment and Deployment Kit)中的一组性能排查工具,核心是 WPR 和 WPA 这两个。2 它们的职责划分很清楚。
WPR 负责采集,WPA 负责分析
- WPR(Windows Performance Recorder) = 采集。它把一组 ETW 提供程序按“配置文件”这个单位打包,开始和停止记录,生成 ETL 文件。命令行版的 wpr.exe 从 Windows 8.1 起就随操作系统内置,无需额外安装。GUI 版(WPRUI.exe)包含在 ADK 中。1
- WPA(Windows Performance Analyzer) = 分析。打开 ETL 文件,用图表和表格进行分析。需要安装 ADK。2
也就是说,只要是用命令行采集,就不需要往客户环境里装额外的软件。用系统自带的 wpr.exe 采集,把 ETL 文件带回来,在自己的 PC 上用 WPA 阅读——这和数据包捕获里“用 pktmon 采集、用 Wireshark 阅读”是同一种分工。
flowchart LR
accTitle: WPR 采集、WPA 阅读的分工
accDescr: 在客户环境用系统自带的 wpr.exe 记录并生成 ETL 文件,带回来用自己 PC 上通过 ADK 安装的 WPA 分析
subgraph customer["客户环境(无需额外安装)"]
wpr["wpr -start → 复现问题 → wpr -stop"] --> etl["ETL 文件"]
end
subgraph office["自己的 PC(已用 ADK 安装 WPA)"]
wpa["在 WPA 中做图表与表格分析"]
end
etl -->|"带回来"| wpa
与 Procmon、PerfView 的分工
先把和相似工具之间的分工整理清楚。
| Process Monitor | PerfView | WPR + WPA | |
|---|---|---|---|
| 能回答的问题 | 哪个进程,对哪个路径,做了什么,结果如何 | .NET 应用的 CPU、GC、分配情况如何 | 整个 OS 里时间消耗在了哪里 |
| 覆盖范围 | 文件、注册表、进程启动的操作日志 | 以托管代码为主 | CPU、等待、磁盘、文件 I/O、电源等整个系统 |
| 适合的症状 | 配置读不到、ACCESS DENIED | 自研 .NET 应用单体的变慢与内存问题 | 整台 PC 变慢、CPU 闲着却很慢、元凶进程不明 |
| 文章 | Procmon 实战指南 | PerfView 实务入门 | 本文 |
如果说 Procmon 是“做了什么”的操作日志,PerfView 是“.NET 内部发生了什么”,那么 WPA 就是跨所有进程审计“时间消耗在了哪里”的工具。
想把自研应用的事件一起记录下来时
ETW 本身的机制,以及怎样把自研应用接入 ETW,在“Windows 事件日志与 ETW 入门”中讲解。只要自研应用会发出 ETW 事件,跟踪里就能一并记录下自研应用的关键节点,比对起来会轻松很多。
不过WPR 只会记录所选记录配置文件中已启用的提供程序的事件。GeneralProfile 里不包含自研的提供程序。要一起记录,就需要准备一个启用自研提供程序的自定义记录配置文件(.wprp),并像 wpr -start GeneralProfile -start MyApp.wprp!MyAppProfile 这样,用 ! 指定 .wprp 文件中的配置文件名来同时使用3。
3. 采集实务(WPR)——start、复现、stop
执行前要先定好的事情
在生产环境中,即使是短时间采集,也不要认为它无条件安全。 ETW 虽然轻量,但大量记录带调用栈的事件仍会消耗一定的 CPU 和内存。请选择对业务影响小的时段,并纳入与普通变更作业相同的审批流程。
能够复现时,在复现前一刻开始、复现后立刻停止,把时间控制在几分钟以内。准备好保存目录,并事先定好 ETL 交给谁、保存期限以及删除方式。关于内容的注意事项见第 9 章。
下面是用 File 模式采集当场可复现问题的例子。 不知道何时发生的问题请看 3.1 节的 Memory 模式,启动和登录过程中的问题请转到第 8 章。
常规采集就是“start → 复现 → stop”
在以管理员身份打开的命令提示符中执行。-profiles 用于事先查看列表,-status 用于查看采集过程中的状态。末尾的 -cancel 只在中途中止并丢弃时使用,不属于常规的保存步骤。
:: 可用的内置配置文件列表
wpr -profiles
:: 1. 开始采集(通用配置文件、文件模式)
wpr -start GeneralProfile -filemode
:: 2. 复现问题(采集过程中用 wpr -status 查看状态)
:: 3. 停止并保存(可以附上问题说明)。
:: 保存目录要事先建好(不存在时 -stop 会保存失败)
mkdir C:\temp 2>nul
wpr -stop C:\temp\slow-pc.etl "复现应用 X 启动过程中整台 PC 变慢的问题"
:: 中途放弃时不保存,直接丢弃
wpr -cancel
记录什么由配置文件决定
传给 -start 的就是配置文件,它是该次排查所需的一组 ETW 提供程序。3 只要记住常用的几个就够了。9
| 配置文件 | 记录的内容 | 使用场景 |
|---|---|---|
GeneralProfile |
CPU 采样、上下文切换、磁盘 I/O 等通用的一整套 | 先用它。还不知道哪里有问题时的第一步 |
CPU |
CPU 使用的详细情况 | 已经确定 CPU 被烧满时 |
DiskIO |
磁盘 I/O 活动 | 怀疑磁盘时 |
FileIO |
文件 I/O 活动 | 需要追到访问了哪个文件时 |
配置文件可以通过并列多个 -start 同时指定(例:wpr -start GeneralProfile -start FileIO -filemode)。3
flowchart TB
accTitle: WPR 采集的流程与模式选择
accDescr: 在手边能复现的问题用文件模式短时间可靠地采集,不知道何时发生的问题用默认的内存模式环形缓冲区守候。启动和登录过程中的问题使用启动跟踪。三者的开始、复现、停止步骤相同
q{"问题何时发生"}
q -->|"在手边能复现"| file["用 -filemode 短时间可靠地采集"]
q -->|"不知道何时发生"| mem["用内存模式(默认、环形缓冲区)守候(3.1 节)"]
q -->|"启动或登录过程中"| boot["启动跟踪(第 8 章)"]
file --> s1["wpr -start → 复现或等待问题发生 → wpr -stop"]
mem --> s1
3.1. Memory 模式与 File 模式——能复现,还是要守候
WPR 的记录目标有两种模式,默认是 Memory 模式(内存中的环形缓冲区)。
Memory 模式用在守候问题发生的时候。 它是从旧事件开始被覆盖的环形缓冲区,因此适合一直开着采集、守候不知何时发生的问题,一旦发生就停止这种用法。
File 模式用在短时间内能够确实复现的时候。 加上 -filemode 就变成 File 模式,全部内容都会记录到连续的文件中。它不会因覆盖而丢失,代价是上限只有磁盘的剩余空间,文件会无止境地膨胀。10
flowchart LR
accTitle: Memory 模式与 File 模式的记录方式
accDescr: Memory 模式记录到内存中的环形缓冲区,旧事件会被覆盖,只留下最近的部分,因此适合守候。File 模式把全部内容留在文件里,但上限只有磁盘剩余空间,适合短时间内能确实复现的场景
ev["ETW 事件流"] --> ring["Memory 模式:环形缓冲区(按从旧到新覆盖 → 只留下最近的部分)"]
ev --> filem["File 模式:全部留在文件中(上限是磁盘剩余空间)"]
ring -.-> use1["适合守候不知何时发生的问题"]
filem -.-> use2["适合短时间内能确实复现的问题"]
选择的大致标准如下。
- 当场能复现 → File 模式。在复现前一刻开始、复现后立刻停止,采集控制在几分钟以内
- 不知道何时发生 → 用 Memory 模式(默认)守候。一发生就立刻
wpr -stop - 即使只用 GeneralProfile 采集几分钟,ETL 也可能达到几百 MB 到 GB 级。文件太大时可能无法用 WPA 分析,所以“采得越久越好”反而适得其反1011
用 GUI 采集时
用 GUI 采集时,只需启动 WPRUI,选好配置文件和 Logging mode,再点 Start/Save 即可。步骤细节整理在官方的 How-to 中。11 若要请客户方的负责人代为采集,也可以以开始、复现、停止的步骤为主,把前提条件和中止时的操作分开写成操作手册。
4. WPA 的基本读法——图表、表格的黄金法则、时间范围的收窄
用 WPA 打开采集到的 ETL,左侧的 Graph Explorer 中会按 System Activity、Computation、Storage、Memory 等类别排列图表缩略图。12 把想看的图表拖到右侧的 Analysis 标签页,上方显示图表,下方显示表格。
要掌握的是列的排列方式、时间范围的收窄、符号配置这三点。不必为每种图表重新学一套操作,从这套通用读法入手即可。
4.1. 列的顺序决定了汇总的切入角度
WPA 的表格里有金色和蓝色两条竖条,金色竖条左侧的列会按其顺序把数据分层(分组),蓝色竖条右侧的列则是汇总值。13
按“Process → Stack”排列就是按进程汇总调用栈,按“Stack → Process”排列就是把使用同一调用栈的所有进程汇总起来——拖动列来重新排序这件事本身就是分析操作。理解了这一点,WPA 的所有表格就都变成同一种读法了。
flowchart LR
accTitle: 表格的黄金法则——两条竖条与各列的作用
accDescr: 金色竖条左侧列的排列顺序决定数据的分层,金色与蓝色竖条之间是显示列,蓝色竖条右侧是汇总值。拖动列来重新排序本身就是一种分析操作
left["金色竖条左侧:分组列(排列顺序 = 层级)"] --> gold["金色竖条"]
gold --> mid["两条竖条之间:显示列"]
mid --> blue["蓝色竖条"]
blue --> right["蓝色竖条右侧:汇总值(Sum、% 等)"]
left -.-> op["拖动列 = 分析操作(Process→Stack 就是按进程汇总调用栈)"]
4.2. 只保留问题发生的时间段
在图表上拖动选择范围,再右键点击“Zoom”,就会切换成只统计该区间。性能排查的原则始终是只看“问题正在发生的区间”(第 9 章)。
4.3. 加载符号,用函数名查看调用栈
要按函数名阅读调用栈,就执行菜单中的 Trace > Load Symbols。14 它默认引用 Microsoft 的公共符号服务器(msdl.microsoft.com),因此只要有互联网连接,Windows 本身的调用栈就能解析出来。
要连自研应用的函数名也看到,就在 Trace > Configure Symbol Paths 中添加自研应用 PDB 所在的文件夹。8 PDB 究竟是什么、为什么即使是发布版构建也必须保存下来,汇总在“PDB(程序数据库)是什么”中。
.NET 要把 NGen 映像和 JIT 代码分开看待
对于 .NET Framework 的 NGen 本机映像,WPR 会在采集时生成 NGen 用的 PDB(.ngenpdb),放到跟踪文件旁边的文件夹里,WPA 会自动引用。8 这是 NGen 映像专用的机制,以 JIT 方式运行的普通 .NET 应用中自己写的代码不在此列。
JIT 代码的地址与函数名的对应关系要靠 CLR 发出的 JIT 事件来解析,所以排查 .NET 应用时,需要准备一个启用 CLR 提供程序(Microsoft-Windows-DotNETRuntime 及其 Rundown)的记录配置文件(.wprp),按第 2 章中自研提供程序同样的做法,以 wpr -start GeneralProfile -start MyDotNet.wprp!配置文件名 的形式一起使用,把 CLR 的事件也包含进跟踪里(本机 WPR 可用的内置配置文件可以用 wpr -profiles 查看)。
在此基础上,为了与源代码行对应,请保存构建时生成的 PDB,并添加到上述符号路径中。
flowchart TB
accTitle: 用函数名阅读调用栈所需的符号解析
accDescr: 执行 Trace Load Symbols 后,Windows 本身从 Microsoft 的公共符号服务器解析,自研应用从添加到符号路径的构建 PDB 解析。NGen 映像靠 WPR 生成的 NGen 用 PDB,JIT 的 .NET 代码靠跟踪中的 CLR JIT 事件与构建 PDB 来解析
load["Trace > Load Symbols"] --> ms["Windows 本身:公共符号服务器(msdl)"]
load --> own["自研应用:添加到 Configure Symbol Paths 的构建 PDB"]
load --> ngen[".NET Framework 的 NGen 映像:WPR 生成的 .ngenpdb"]
load --> jit["JIT 的 .NET 代码:跟踪中的 CLR JIT 事件 + 构建 PDB"]
4.4. 决定排查 CPU、等待还是 I/O
准备好之后,先看问题区间内 CPU 是高还是低。高就去第 5 章的 Sampled。低的时候也要先确认有没有某个核心、某条线程被跑满,如果也没有,就用第 6 章的 Precise 追查等待。怀疑磁盘就转到第 7 章。
flowchart TB
accTitle: 从症状选择 WPA 图表的分支
accDescr: 缩放到问题区间后,CPU 高就去 CPU Usage Sampled,CPU 低却很慢就先确认核心是否被跑满再做 CPU Usage Precise 的等待分析,怀疑磁盘就转到 Disk Usage 与 File IO
zoom["缩放到问题发生的区间"] --> cpu{"该区间的 CPU 如何"}
cpu -->|"高"| sampled["第 5 章:用 CPU Usage (Sampled) 看“谁在哪个函数里烧 CPU”"]
cpu -->|"低却很慢"| core{"有没有单核心、单线程被跑满"}
core -->|"有"| sampled
core -->|"没有"| precise["第 6 章:用 CPU Usage (Precise) 看“当时在等什么”"]
cpu -->|"怀疑磁盘"| disk["第 7 章:用 Disk Usage / File I/O 锁定元凶"]
5. CPU 高的情况——用 CPU Usage (Sampled) 查“谁在哪个函数里烧 CPU”
本章要找的,是占用了 CPU 时间很大比例的调用路径。
如果 CPU 被跑满,要看的就是 CPU Usage (Sampled)。它是大约每 1 毫秒在所有 CPU 上记录一次“此刻正在执行的是哪个进程的哪个调用栈”的采样数据,采样数的比例直接就是 CPU 时间的构成。6
flowchart LR
accTitle: CPU Usage Sampled 的原理
accDescr: 大约每 1 毫秒在所有 CPU 上记录正在执行的调用栈,汇总采样得到的比例就是 CPU 时间的构成。从进程往下到线程、调用栈、函数逐层阅读。在采样间隙结束的短暂活动不会被记录下来
tick["大约每 1 ms 一次的中断"] --> snap["在所有 CPU 上记录“此刻正在执行的调用栈”"]
snap --> agg["汇总采样(比例 = CPU 时间的构成)"]
agg --> drill["Process → Thread → Stack → 函数,逐层下钻"]
snap -.-> miss["在采样间隙结束的短暂活动不会被记录下来"]
从进程往下到调用栈和函数
- 从 Graph Explorer 的 Computation 把 CPU Usage (Sampled) 放到 Analysis 标签页,选择预设 Utilization by Process, Stack。5
- 按 Weight(或 Count)从大到小查看进程。任务管理器里那个“50%”的真面目,首先在进程这一层就能看清。
- 展开元凶进程的 Stack 列。调用栈是按树形汇总的,沿着分叉后数字不会大幅减少的那条路往下走,就能走到正在烧 CPU 的函数。只要符号解析成功,就能一直定位到自研代码的哪个函数。
- 如果嫌一层层展开树太麻烦,就把图表显示切换成 Flame(火焰图)。它以横向宽度 = CPU 时间占比来绘制,哪条调用路径占主导一眼就能看出。CPU Usage (Sampled) 也备有 Flame by Process, Stack 这个预设。13
Sampled 测不出单次的准确耗时
既然是采样,在采样间隙就结束的短暂活动就不会被记录下来。6 请记住,它是用来看“总体上是哪里用掉了 CPU”的工具,而不是测量单次准确执行时间的工具。
6. CPU 低却很慢的情况——CPU Usage (Precise) 与等待分析
6.1. 做等待分析之前,先确认有没有单个核心被跑满
“整体 CPU 使用率低”并不等于“CPU 不是瓶颈”。 在 16 核的 PC 上,把一个核心跑满的串行处理(一条 UI 线程在全速运转的状态),在整体上也只显示为 6% 左右。
先用第 5 章的 Sampled,或者 CPU Usage (Precise) 的 Utilization by CPU,确认有没有某个核心、某条线程被跑满。如果也没有被跑满,就可以认为处理不是用不上 CPU,而是在等什么,进入等待分析。从这里开始用到的就是 CPU Usage (Precise)。
6.2. 把“等待的时间”和“被唤醒之后等 CPU 的时间”分开
Sampled 是采样,而 Precise 是上下文切换(线程切换)的完整记录。线程进入等待、被某一方唤醒(Ready)、重新上 CPU——这一来一回都逐行留了下来,可以读到下面这些列。74
| 列 | 含义 |
|---|---|
| NewThreadStack | 该线程是在哪个调用栈上进入等待的(= 做了什么之后停住) |
| Waits (us) | 等待的时间 |
| Ready (us) | 从被唤醒到真正上 CPU 之间被迫等待的时间(CPU 的争抢) |
| ReadyingProcess / ReadyingThreadId | 唤醒(解除等待)该线程的进程和线程 |
| ReadyThreadStack | 唤醒方是在哪个调用栈上唤醒它的 |
flowchart LR
accTitle: 一次等待往返与各列的对应关系
accDescr: 线程在 NewThreadStack 留下的调用栈上进入等待,等待 Waits 所记录的时长。被某一方唤醒后,ReadyingProcess 与 ReadyThreadStack 会留下对方的信息,再经过 Ready 所记录的争抢 CPU 的时间后重新执行
run1["执行中"] -->|"进入等待(留在 NewThreadStack)"| waitst["等待(Waits (us))"]
waitst -->|"被某一方唤醒(ReadyingProcess / ReadyThreadStack)"| ready["Ready(等待争抢 CPU)"]
ready -->|"上 CPU"| run2["重新执行"]
6.3. 从被延迟的线程出发,依次追出唤醒它的一方
阅读顺序如下。4
1. 准备好图表和列
应用预设 Utilization by Process, Thread,并把 NewThreadStack、ReadyThreadStack 添加到列中。
2. 选出执行被延迟操作的线程
首先确定 UI 线程或处理目标请求的线程等,当时正在执行被延迟操作的那条线程。如果只按 Waits 合计从大到小看,消息泵、定时器这类故意一直等待的正常线程就会排在前面。
目标线程的 CPU Usage (ms) 大,就是第 5 章的 CPU 问题;Waits 占主导,就是等待问题。
3. 用 NewThreadStack 看“做了什么之后停住”
展开 NewThreadStack。如果是 WaitForSingleObject 或 EnterCriticalSection,就是在等锁;如果停在 ReadFile 等同步 I/O 里,就是在等 I/O;如果停在套接字接收里,就是在等对方响应。
4. 用 ReadyThreadStack 看“是谁解除了等待”
展开 ReadyThreadStack,确认 ReadyingProcess / ReadyingThreadId。如果是被内核的 KiTimerExpiration 唤醒的,就说明是定时器(一直睡到超时);如果是被 I/O 完成处理唤醒的,就印证了它当时是在等 I/O。4
5. 对唤醒方重复同样的排查
如果唤醒方是另一条线程或另一个进程,接下来就用同样的步骤排查那条线程。
例如会形成这样的连锁:A 在等 B 释放锁,B 在等 C 的 RPC 响应,而 C 在等磁盘 I/O。把这条链一路追到源头,就是延迟的关键路径。7
flowchart LR
accTitle: 等待分析中要追的关键路径链条
accDescr: 先用被延迟线程 A 的 NewThreadStack 看它做了什么之后停住,再用 ReadyThreadStack 与 ReadyingProcess 找出唤醒它的 B,对 B 也用同样的步骤排查,一直追到源头的磁盘 I/O
a["线程 A(被延迟的操作)"] -->|"NewThreadStack:停在等锁"| b["线程 B(正持有锁)"]
b -->|"NewThreadStack:等 RPC 响应"| c["进程 C"]
c -->|"NewThreadStack:等同步 I/O"| d["磁盘 I/O(源头)"]
d -.->|"完成后唤醒 C"| c
c -.->|"响应唤醒 B"| b
b -.->|"释放锁唤醒 A(出现在 ReadyThreadStack 中)"| a
把等待的原因接到设计的改进上
例如在“改成多线程了却没变快”的项目里,用这套步骤就能直接看到所有工作线程都排在同一把锁上的情形。用设计来规避锁竞争的内容写在“多线程实务最佳实践 .NET 篇”里,用完成通知取代同步 I/O 等待的 Windows 机制写在“I/O 完成端口(IOCP)与 .NET 线程池”里。用 WPA 查明“在等谁”,再用这些设计理论去修正,是一条连贯的流程。
7. 磁盘与文件 I/O——锁定“有人在没完没了地扫磁盘”
“整台 PC 变慢”的常客元凶不是 CPU,而是磁盘。用 Storage 类别中的 Disk Usage 和 File I/O 来排查。15
7.1. 用 Disk Usage 把“设备的处理”和“排队”分开
Disk Usage 是磁盘 I/O 的记录。首先要区分下面这两列。6
| 列 | 表示的时间 |
|---|---|
| Disk Service Time | 磁盘设备处理这次 I/O 实际花掉的时间 |
| IO Time | I/O 进入操作系统队列之后到完成为止的时间 |
IO Time 会因为多出排队的部分,必然大于等于 Service Time。如果 IO Time 比 Service Time 长出很多,就说明这次 I/O 是在排队等候。6
不过,不要只凭时间差就断定原因。到底是别的进程排起了队,还是自己的大量 I/O 堆在了一台慢设备上,此时还无法确定。先用 Service Time 看设备本身的响应,再按进程、路径、调用栈拆开构成来确定。
7.2. 哪个进程向哪个文件发出了 I/O
为此用 Utilization by Process, Path Name, Stack 预设,按 IO Time 或 Size 从大到小查看哪个进程、对哪个文件、从哪个调用栈发出了 I/O。15 现场常见的答案是这两种。
- 杀毒软件在把所有文件扫一遍。表现为在应用启动缓慢的时间段里,杀毒软件的进程发出了大量读取。进程名、路径、数据量的证据可以直接作为商讨排除设置的材料。
- 另一个进程在大量写入。备份、索引器、日志写得太多等等。写入何时真正落到磁盘上要看缓存管理器,因此“写入的那一刻”和“磁盘繁忙的那一刻”会错开——这一点在“缓存管理器——你的 WriteFile 何时才能到达磁盘”中讲解。
7.3. 用 File I/O 看到达磁盘之前的慢
File I/O 是更上一层的记录,也就是应用发出的文件操作(Create/Read/Write 等)的记录,用 Duration by Process, Thread, Type 等预设可以按文件名、按操作汇总时间。15
在到达磁盘之前就被文件系统或筛选器驱动程序吃掉时间的情况不会出现在 Disk Usage 上,所以“Disk Usage 很平静,File I/O 却很慢”这种矛盾本身就是线索。想从同步 I/O 与异步 I/O 的机制了解起的读者,可以看“同步 I/O 与异步 I/O——OVERLAPPED 的真正含义”。
flowchart TB
accTitle: File IO 与 Disk Usage 观察的层次差异
accDescr: 应用的文件操作经过文件系统和筛选器驱动程序,再从操作系统的 IO 队列到达磁盘设备。File IO 记录上层的操作,Disk Usage 记录到达磁盘的 IO,IO Time 与 Disk Service Time 之差表示排队的时间
app["应用:ReadFile / WriteFile"] --> fio["文件系统与筛选器驱动程序(File I/O 观察的层)"]
fio --> queue["操作系统的 I/O 队列"]
queue --> dev["磁盘设备(Disk Usage 观察的层)"]
fio -.-> n1["在这里耗掉的时间不会出现在 Disk Usage 上"]
queue -.-> n2["IO Time − Disk Service Time = 排队的时间"]
dev -.-> n3["Disk Service Time = 设备的处理时间"]
7.4. 怀疑内存不足时,也要确认物理内存
“是不是内存不足在换页”这条思路,在进入 WPA 之前就能用任务管理器和资源监视器做初步排查。
不要只看已提交内存就否定内存不足。 即使已提交量还有余量,也可能因为物理内存吃紧而导致工作集被削减、硬错误持续发生。请一并确认可用的物理内存,以及资源监视器中的“硬错误/秒”。读法请参考“Windows 的“内存使用量”究竟表示什么”。
8. 启动和登录很慢——启动跟踪的入口
“开机要 3 分钟”这一类问题,往往在你手动敲 wpr -start 之前就已经结束了。WPR 提供了启动跟踪,可以预先设置成下次启动时由操作系统自动开始记录。3
预置下次启动的记录,重启后保存
与第 3 章一样,先取得采集的批准并定好 ETL 的处理方式,然后在以管理员身份打开的命令提示符中操作。
:: 1. 预置下次启动时的自动记录
wpr -boottrace -addboot GeneralProfile -filemode
:: 2. 重启(复现启动很慢的现象)
:: 3. 启动后停止记录并保存(预置也会被解除)
mkdir C:\temp 2>nul
wpr -boottrace -stopboot C:\temp\boot.etl "开机要花 3 分钟的问题"
flowchart LR
accTitle: 启动跟踪的流程
accDescr: 用 addboot 预置下次启动时的自动记录后重启,启动时操作系统会自动开始记录。登录后用 stopboot 保存,预置也随之解除。要取消则用 cancelboot 解除
add["用 wpr -boottrace -addboot 预置"] --> rebootpc["重启(复现启动很慢)"]
rebootpc --> auto["启动时由操作系统自动开始记录"]
auto --> stop2["登录后用 -stopboot 保存(预置也会解除)"]
add -.-> cancel["要取消就用 -cancelboot"]
采到之后,仍然走同样的“CPU、等待、I/O”区分
过去由 xbootmgr 承担的启动与关机计时,在现在的 WPR 中也可以用 -onoffscenario Boot 之类的选项来完成。3
采到的跟踪可以用前面各章相同的工具组合来阅读。用 Processes 图表按时间顺序观察哪个进程在什么时候诞生,缩放到启动卡住的时间段,再分类是 CPU、是等待还是磁盘——就能看出启动应用在串行地等待某个东西、服务的启动卡在某个特定 I/O 上之类的形态。
启动分析本身就是一个很深的专门领域,所以本文只讲到“手动来不及的现象用 WPR 也能采到”这个入口。请先从用 GeneralProfile 的启动跟踪把握全局开始。
9. 实务套路——分类 → 缩放 → 调用栈的反复
工具讲清楚之后,再把整个排查的套路整理一下。先确定时刻、收窄区间,分类之后再追调用栈,这个顺序对任何症状都通用。
从确定时刻到验证假设
- 确定现象发生的时刻。不能停在“当时很慢”,要精确到“10:23:40~10:24:10 这段时间很慢”。应用日志、事件日志、操作者的记录,什么都可以。只要自研应用往 ETW 或事件日志里写了关键节点,跟踪中的这些事件就能直接当作时刻的标桩。
- 只缩放到那个区间。对整条跟踪做汇总会被平均值抹平,关键的异常反而被稀释。WPA 的分析始终是“异常区间”与“正常区间”的对比。
- 最先分类“是 CPU、是等待,还是 I/O”。看 CPU Usage (Sampled),在烧 CPU 就去第 5 章。没在烧就去看 CPU Usage (Precise) 的 Waits(第 6 章)。Disk Usage 的 IO Time 膨胀就去第 7 章。一开始就走过这个三岔路口,就不会迷路。
- 反复做假设 → 缩放 → 调用栈。觉得“是不是杀毒软件”,就缩小到那个进程,用调用栈取证。猜错了就换下一个假设。在下钻到调用栈取得佐证之前不下结论,是这类排查的纪律。
flowchart LR
accTitle: 性能排查的反复循环
accDescr: 确定现象发生的时刻并缩放到该区间,分类是 CPU、等待还是 IO,提出假设缩小范围,再用调用栈取证。取到佐证就确定原因,猜错就换下一个假设继续循环
time["确定现象的时刻"] --> zoomstep["缩放到该区间"]
zoomstep --> triage["分类是 CPU、是等待还是 I/O"]
triage --> hypo["提出假设并缩小范围"]
hypo --> stack["用调用栈取证"]
stack -->|"取到了佐证"| fix["确定原因 → 着手处理"]
stack -->|"猜错了"| hypo
ETL 的处理方式,要在采集前定好
ETL 文件里广泛地留下了系统内部的样貌:所有进程的名称、打开过的文件路径、加载过的模块,以及(视配置文件而定)注册表的键名等。
GeneralProfile 的标准采集不包含通信内容之类的数据主体,但一旦启用了自定义提供程序,其事件的有效负载(应用记录下的字符串等)就会原样进入文件。
请先确认启用的提供程序会输出什么,再把它当作对外足够敏感的机密文件来对待。和数据包捕获一样,请把必要的最小限度采集、与接收方达成一致、保存期限与删除纳入操作流程。
flowchart LR
accTitle: ETL 文件中留下的内容与处理方式
accDescr: ETL 中留下所有进程名、打开过的文件路径、模块,视配置文件还会留下注册表键名,启用自定义提供程序后连其有效负载也会进入。要当作机密文件,把必要的最小限度采集、与接收方达成一致、保存期限与删除写进流程
etl["ETL 文件"] --> a1["所有进程名、文件路径、模块"]
etl --> a2["(视配置文件而定)注册表键名"]
etl --> a3["自定义提供程序的有效负载(应用记录下的字符串)"]
a1 -.-> rule["当作机密来处理:必要的最小限度采集、与接收方达成一致、保存期限与删除"]
a2 -.-> rule
a3 -.-> rule
10. 小结
WPR/WPA 的排查,按下面三个阶段来想就能理清。
- 只采集需要的区间。 用 Windows 8.1 起内置的 wpr.exe 采集,把 ETL 拿到自己机器上的 WPA 里阅读。基本形是
wpr -start GeneralProfile -filemode→ 复现 →wpr -stop trace.etl。能复现就用 File 模式控制在几分钟以内,要守候就用 Memory 模式。启动过程中的慢用wpr -boottrace。并不是采得越久越好,ETL 里的内部信息要当作机密来对待。 - 收窄时间段,选定排查方向。 在 WPA 中,金色竖条左边是分组,蓝色竖条右边是汇总值。掌握列的顺序、区间的缩放和符号配置,区分是 CPU、是等待还是 I/O。自研应用要准备好 PDB,排查 .NET 的 JIT 代码时还要确认采集时的 CLR 事件。
- 一直追到调用栈,验证假设。 CPU 高就用 Sampled 从进程往下钻到函数。即使整体 CPU 低,也要先确认有没有单个核心、单条线程被跑满,如果没有再用 Precise 追查等待。磁盘方面不要只凭时间差就决定原因,还要看设备的响应和 I/O 发起方的构成。
等待分析中要追的是 NewThreadStack(做了什么之后停住) → Waits(等了多久) → ReadyingProcess、ReadyThreadStack(是谁唤醒的)。对唤醒方也照样排查,就能把延迟的连锁一路追到源头。
WPA 一开始可能会让人被界面的信息量压倒。即便如此,只要抓住“列的顺序就是汇总的切入角度”“Sampled 看的是 CPU 用在了哪里,Precise 看的是在等谁”,剩下的就是确定时刻、缩放、分类、查看调用栈的反复。下次再遇到“CPU 还有富余却很慢”的咨询时,请按这个顺序采集跟踪并读下去。
相关文章
- 用 PerfView 与 dotnet-trace 定位“变慢”的原因——.NET 性能排查实务入门
- Process Monitor(ProcMon)实战指南——10 分钟定位“配置未生效”“ACCESS DENIED”问题
- Windows 事件日志与 ETW 入门——让业务应用的日志接入操作系统标准机制
- Windows 的“内存使用量”究竟表示什么——正确解读 Working Set、Private Bytes、Commit 与页面文件
- Windows 的处理器计划设置 - 后台服务与 P 核 / E 核
- PDB(程序数据库)是什么——理解调试信息、符号与 Source Link
相关咨询领域
小村软件有限公司承接“整台 PC 变慢但查不出原因”“CPU 还有余量应用却很慢”“只有特定环境启动极慢”这类系统整体性能问题的调查。从基于 WPR/WPA 的采集设计(在哪个环境、用哪个配置文件、采集多久)到跟踪分析,再到修正作为原因的应用侧、配置侧问题,我们一并承接。
参考链接
-
Microsoft Learn, Introduction to WPR. 关于 WPR 是基于 ETW 的性能记录工具、命令行版的 WPR.exe 随 Windows 8.1 及以上的操作系统一同提供且无需额外安装、它与 GUI 版 WPRUI.exe 的关系,以及记录配置文件的思路。 ↩ ↩2 ↩3
-
Microsoft Learn, Windows Performance Analyzer. 关于 WPA 包含在 Windows ADK 中,是从 WPR、Xperf 等记录的 ETW 事件生成图表与数据表的分析工具,并且可以打开任意 ETL 文件进行分析。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, WPR Command-Line Options. 关于 wpr -start/-stop/-cancel/-status/-profiles 的语法、-filemode(默认是内存模式)、同时指定多个配置文件、用 -boottrace 做启动跟踪(addboot/stopboot/cancelboot),以及用 -onoffscenario 记录 Boot 等 On/Off 转换的方法。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, CPU Analysis. 关于 CPU Usage (Precise) 图表各列(NewThreadStack、ReadyThreadStack、ReadyingProcess、Waits 等)的定义,展开 ReadyThreadStack 并顺着 ReadyingProcess/ReadyingThread 追到等待根本原因的步骤,以及分辨 KiTimerExpiration(定时器等待)与 I/O 完成所致唤醒的方法。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Troubleshoot processes and threads by using WPR and WPA. 关于高 CPU 占用时按 Process → Stack 阅读 CPU Usage (Sampled) 的配置、等待分析(Wait analysis)中使用 CPU Usage (Precise) 的 Readying Process、Readying Thread、Readying Stack 与 Wait 列的配置等,按症状列出的配置文件与图表对应表。 ↩ ↩2
-
Microsoft Learn, Exercise 2 - Evaluate Fast Startup Using Windows Performance Toolkit. 关于 CPU Usage (Sampled) 是大约 1 毫秒间隔的采样、采样之间的短暂活动不会被记录,按进程 → 线程 → 调用栈下钻确定 CPU 消耗构成的步骤,以及 Disk Usage 的 IO Time(含排队时间)与 Disk Service Time(磁盘的处理时间)的含义。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Exercise 3 - Understand Critical Path and Wait Analysis. 关于关键路径分析的思路(Running、Ready、Waiting 的分类)、CPU Usage (Precise) 表中 NewThreadStack、ReadyThreadStack、ReadyingProcess、Waits、Ready 等列的含义,以及依次追查唤醒方线程以厘清延迟连锁的步骤。 ↩ ↩2 ↩3
-
Microsoft Learn, Loading Symbols. 关于未设置 _NT_SYMBOL_PATH 时 WPA 默认引用 Microsoft 公共符号服务器(msdl.microsoft.com)、添加自研组件 PDB 路径的方法,以及 WPR 会把 .NET 托管符号用的 PDB 生成到跟踪旁边的 .ngenpdb 文件夹中并由 WPA 自动引用。 ↩ ↩2 ↩3
-
Microsoft Learn, Built-in Recording Profiles. 关于 WPR 内置记录配置文件的一览(CPU usage、Disk I/O activity、File I/O activity、Registry I/O activity、Networking I/O activity 等)以及各配置文件所记录的内容。 ↩
-
Microsoft Learn, Logging Mode. 关于记录模式分为 File(连续文件)与 Memory(内存中的环形缓冲区)且默认为 Memory、Memory 适合不知何时发生的问题且旧事件会被覆盖、File 的唯一上限是磁盘剩余空间且文件太大时可能无法用 WPA 分析。 ↩ ↩2
-
Microsoft Learn, WPR How-to Topics. 关于在 WPRUI 中开始与停止记录的步骤、配置文件与详细级别以及 Logging mode 的选择,还有长时间记录会让文件变得巨大、可能无法用 WPA 分析因而应选择 Memory 模式的提醒。 ↩ ↩2
-
Microsoft Learn, Graph Explorer. 关于 Graph Explorer 窗口按 System Activity、Computation、Storage、Memory 等类别排列图表缩略图,以及把图表拖到 Analysis 标签页后与表格一同显示。 ↩
-
Microsoft Learn, Graphs (WPA Features). 关于 WPA 的 Flame(火焰图)显示、金色竖条左侧的列为分组而蓝色竖条右侧为汇总值的表格构成,以及 CPU Usage (Sampled) 的 Flame by Process, Stack 预设。 ↩ ↩2
-
Microsoft Learn, Load Symbols or Configure Symbol Paths. 关于从 WPA 的 Trace 菜单用 Load Symbols 加载符号,以及在 Configure Symbol Paths 对话框中设置和修改符号路径的步骤。 ↩
-
Microsoft Learn, List of WPA Graphs. WPA 中可用图表的一览。关于 Disk Usage 的 IO Time by Process, IO Type、Service Time by Process, Path Name, Stack、Utilization by Process, Path Name, Stack,以及 File I/O 的 Duration by Process, Thread, Type 等预设。 ↩ ↩2 ↩3
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
睡眠恢复后就出故障的应用——电源事件机制与扛得住恢复的业务应用设计
打开笔记本电脑时业务应用的通信已经断开——原因是设计没有考虑睡眠。本文依据一手资料讲解 WM_POWERBROADCAST 的通知流程、Modern Standby 的行为、断开与重连的设计、睡眠抑制以及调查命令。
DllMain 与加载器锁——“DLL 初始化里什么都别做”的真正原因
为什么不能在 DllMain 里调用 LoadLibrary 或与线程同步。本文依据一手资料,从串行化全部 DLL 通知的加载器锁机制,讲到死锁成立的典型场景、延迟初始化等正确设计,以及挂起的调查步骤。
“无响应”的真相——Windows 如何判定应用卡死,以及不会卡死的设计
Windows 的“无响应”是操作系统在窗口 5 秒未取出消息时作出判定、并换成幽灵窗口的机制。本文讲解判定的内部动作、卡死的常见原因、把繁重处理移出 UI 线程的设计,以及挂起的调查步骤。
解读 Windows 错误码——Win32 错误、HRESULT、NTSTATUS 的三层结构
出现 0x80004005 时,先分解再搜索。讲解 Win32 错误、HRESULT、NTSTATUS 的三层结构,0x8007xxxx=Win32 错误重新封装这一最重要模式,以及用 err.exe 和 PowerShell 查询的方法。
Windows 的“内存使用量”究竟表示什么——正确解读 Working Set、Private Bytes、Commit 与页面文件
任务管理器的内存、Working Set、Private Bytes 与 Commit 并不是同一个值。本文讲解 Windows 虚拟内存与物理内存的关系、页面文件的作用,以及排查内存不足和内存泄漏时应当关注的指标。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
常见问题
汇总了咨询这一主题时常见的问题。
- WPR 和 WPA 从哪里获取?在无法安装软件的客户环境也能用吗?
- 采集工具 wpr.exe(命令行版)从 Windows 8.1 起就随操作系统内置,无需额外安装即可使用。GUI 版的 WPRUI 和分析工具 WPA(Windows Performance Analyzer)包含在 Windows ADK(Windows Assessment and Deployment Kit)中,需要单独安装。实务中只要按“在客户环境只用系统自带的 wpr.exe 采集 ETL 文件,带回来用自己机器上的 WPA 分析”来分工,即使在不能加装软件的现场也能完成系统整体的性能排查。
- 任务管理器显示 CPU 还有余量,为什么还是慢?用 WPA 能看出什么?
- CPU 使用率不高却很慢时,处理并不是用不上 CPU,而是“在等某个东西”而停住了。典型情况有锁的争用、等同步 I/O 完成、等其他进程响应等。任务管理器只显示使用率这个结果,而 WPA 的 CPU Usage (Precise) 基于每次上下文切换的记录,能显示线程是在哪里开始等待(NewThreadStack)、等了多久(Waits)、以及是谁把它唤醒的(ReadyingProcess、ReadyThreadStack)。顺着让它等待的对象一路追下去,就能把“变慢的元凶”定位到函数级别。
- 跟踪要采集多长时间?文件会不会变得非常大?
- 如果问题能复现,基本做法是在复现前一刻开始、复现后立刻停止,把时间控制在几分钟以内。WPR 默认是记录到内存环形缓冲区的 Memory 模式,旧事件会被覆盖,因此适合守候那些不知道何时发生的问题。加上 -filemode 的 File 模式会把全部内容连续写入文件,虽然不会被覆盖,但上限只有磁盘剩余空间,文件太大时可能无法用 WPA 分析。请这样区分使用:长时间守候用 Memory 模式,短时间且能确实复现用 File 模式。
- PerfView 和 WPA 该怎么分工使用?
- 两者都是处理 ETW 跟踪的工具,但擅长的领域不同。PerfView 对 .NET 运行时理解更深,擅长 GC、分配、JIT 等托管应用特有的调查。WPA 则适合用图表和表格横向阅读整个 OS 的 CPU、磁盘、文件 I/O、电源等,是“不是某个应用而是整台 PC 变慢”“牵扯多个进程”“怀疑问题出在应用之外(杀毒软件、驱动程序、其他进程)”时的首选。大致标准是:自研 .NET 应用单体的变慢用 PerfView,系统整体的变慢用 WPR/WPA。
- 在客户的生产环境运行 WPR 没问题吗?
- 短时间采集在实务中很常见,但并非无条件安全。ETW 虽然轻量,但要记录大量带调用栈的事件,仍会消耗一定的 CPU 和内存。请把“在复现操作前一刻开始、复现后立刻停止”“采集控制在几分钟以内”“选择对业务影响小的时段”这些考虑,纳入与普通变更作业相同的审批流程。另外,ETL 文件中包含进程名、文件路径、可执行文件信息等系统内部信息,因此带出公司时的处理方式(最小化、保存期限、删除)也应事先定好。