用 WPR 分析性能问题该从哪看起:从 CPU、内存到耗时路径的实战路线
一篇从入门到进阶的 WPR 使用指南,讲清楚性能分析应先看哪些指标、如何针对 CPU 高、内存多、进程耗时长定位根因,并给出可复用的排查流程
很多同学第一次用 WPR(Windows Performance Recorder)会遇到同一个问题:
轨迹录出来了,图也很多,但不知道应该先看哪一个指标,更不知道怎么从“现象”走到“根因”。
这篇文章我给你一个可执行的分析顺序,并把常见的三类问题拆开讲:
- CPU 占用高怎么查;
- 内存占用大怎么查;
- 进程耗时长(卡顿、慢请求、启动慢)怎么查。
目标是:让你不再“看图发呆”,而是有一条从浅入深的路径。
1. 先建立一个核心原则:先定“问题类型”,再选“主指标”
性能分析最容易犯的错是:一上来就钻调用栈。
更高效的方式是先分型:
- 资源是否饱和:CPU、磁盘、内存、网络谁先到顶;
- 时间花在哪里:用户感觉慢的那几秒,线程到底在跑、在等,还是被阻塞;
- 代价由谁承担:哪个进程、哪个线程、哪个模块在消耗资源。
你可以记一个口诀:
先看总量,再看分布;先看进程,再看线程;先看等待,再看执行。
2. 录制前准备:没有“好样本”,就没有“好结论”
2.1 明确场景边界
至少写清楚三件事:
- 问题动作是什么(例如“点击导出按钮后卡 8 秒”);
- 问题时间窗(从哪一秒开始,到哪一秒结束);
- 预期与实际(预期 1 秒,实际 8 秒)。
2.2 录制建议
- 先做一次短录制(30 秒到 2 分钟),覆盖问题动作即可;
- 尽量在“问题可稳定复现”时录,不要边猜边录;
- 同场景建议录两份:
- 一份“正常样本”;
- 一份“异常样本”; 对比往往比单看更快。
2.3 Profile 选择思路(通用)
- 初查:CPU + Disk I/O + File I/O + Memory 的通用组合;
- 深挖 CPU:打开采样与调用栈;
- 深挖等待:关注线程调度、上下文切换、DPC/ISR(驱动或中断相关时)。
3. 从浅入深的总流程(建议固定成团队 SOP)
第 0 步:先做“时间对齐”
在 WPA 打开 trace 后,先把时间轴缩到问题区间。所有后续观察都围绕这一段,不要全局乱看。
第 1 步:看系统总览(谁最可疑)
优先看:
- CPU Usage(总体与各进程);
- Commit/Working Set(内存压力);
- Disk Usage / Queue Length(磁盘是否排队);
- Hard Faults(是否在频繁缺页)。
这一步的目标不是下结论,而是给出“主方向”:
- CPU 型瓶颈;
- 内存/分页型瓶颈;
- I/O 等待型瓶颈;
- 混合瓶颈。
第 2 步:下钻到进程与线程
锁定可疑进程后,继续看:
- 该进程的 CPU 曲线是否与卡顿窗口重合;
- 关键线程是 Running 多还是 Waiting 多;
- 若 Waiting 多,主要等待类型是什么(I/O、锁、事件、网络)。
第 3 步:才进入调用栈
只有当你知道“哪个线程在那段时间有问题”后,再看调用栈。
否则你会在海量栈里迷失,且很容易误判“最热函数就是根因”。
4. 场景一:CPU 高,该从哪些指标开始?
CPU 高并不一定代表“代码算太多”,也可能是忙等、争锁、频繁切换、驱动中断。
4.1 第一个观察面:CPU Usage (Sampled)
先回答三件事:
- 是单核打满还是多核普遍高;
- 是单进程高还是系统普遍高;
- 峰值是持续高还是脉冲高。
4.2 第二个观察面:按进程/线程分解
看“哪个线程”贡献了主要 CPU:
- 若是业务线程:大概率是算法/循环/解析/序列化等热点;
- 若是 GC 线程:需要看分配速率与对象生命周期;
- 若是系统线程或中断:可能是驱动、网络包风暴、设备问题。
4.3 第三个观察面:调用栈与符号
在热点线程上看栈,重点找:
- 自家模块函数是否在热点路径;
- 是否大量时间停在某个库函数(压缩、加密、正则、JSON);
- 是否存在重复计算或过深递归。
4.4 常见误区
- 误区 1:CPU 高就一定是“慢”——如果用户路径不受影响,可能只是后台任务;
- 误区 2:只看 Top 函数——要看调用链与调用次数;
- 误区 3:忽略上下文切换——线程切得太频繁也会“看起来 CPU 高但业务没推进”。
5. 场景二:内存多,该从哪些指标开始?
“内存多”至少分三类:
- 正常缓存增长;
- 暂时峰值(可回落);
- 持续增长(疑似泄漏)。
5.1 第一层:先看系统压力而不是只看进程大小
关键指标:
- Commit(提交量)是否逼近上限;
- Available Memory 是否持续下降;
- Hard Faults 是否上升;
- 页面置换是否频繁。
如果系统已出现高缺页,用户卡顿可能来自分页 I/O,而不只是“进程看起来大”。
5.2 第二层:看进程内存构成
常见拆分:
- Private Working Set;
- Commit Size;
- Image/File 映射;
- 堆分配热点(如可见)。
判断思路:
- Working Set 高但 Commit 稳定:可能是活跃缓存,不一定泄漏;
- Commit 持续单调上涨且不回落:优先排查泄漏或对象滞留;
- Hard Faults 与卡顿同频:优先排查分页和磁盘压力。
5.3 第三层:关联业务时间窗
一定要把内存变化放到“操作时刻”上看:
- 哪个动作触发了增长;
- 增长后是否有回收;
- 回收延迟是否导致用户可感知卡顿。
6. 场景三:进程耗时长(启动慢、接口慢、UI 卡)怎么查?
这类问题本质是“时间去哪了”。
6.1 先拆分阶段
例如一次慢请求 8 秒,可以拆成:
- CPU 执行 1.2 秒;
- 磁盘等待 2.5 秒;
- 网络等待 3.0 秒;
- 锁等待 1.3 秒。
你会发现,优化方向完全不同。
6.2 关键视角:Running vs Waiting
对慢线程来说:
- Running 高:优化算法、减少计算、改数据结构;
- Waiting 高:排查锁竞争、I/O 延迟、外部依赖。
6.3 启动慢专用思路
如果是进程启动慢,常见切入:
- 模块加载是否过多;
- 首次 JIT/初始化是否过重;
- 配置读取与文件扫描是否串行阻塞;
- 安全软件或签名校验是否造成额外等待。
7. 一套可复用的 WPR 排查模板(建议收藏)
每次分析都按下面填:
- 问题定义:动作、时长、影响范围;
- 时间窗:trace 中起止时间;
- 主瓶颈判定:CPU / Memory / I/O / Lock;
- Top 进程:资源占比与峰值时刻;
- Top 线程:Running/Waiting 比例;
- 热点栈:函数链路(不少于 3 层);
- 根因假设:最多 2 个;
- 验证动作:改配置、降采样、替代实现、AB 对比;
- 结论与收益:优化前后指标对比。
这样做的好处是:团队复盘时不再“各说各话”。
8. 如何从“会看图”进阶到“能给优化方案”
8.1 建立自己的“基线库”
记录常见场景的正常区间:
- 冷启动耗时;
- 常见操作 CPU 峰值;
- 内存稳态区间。
没有基线,就很难判断“高”到底高到什么程度。
8.2 做对比而不是做单点
至少做两种对比:
- 优化前 vs 优化后;
- 异常机器 vs 正常机器。
WPR/WPA 最大价值就在“可量化对比”,不是“主观感觉变快”。
8.3 把结论写成可验证假设
不要写“可能是 CPU 问题”,要写:
- “在 12:01:23~12:01:27,线程 T42 在
ParseLargeJson占用 68% CPU; 将 JSON 分片后,该段下降到 24%,总耗时从 8.1s 到 3.4s。”
这才是可落地的工程结论。
9. 常见问题速查(FAQ)
Q1:看到 CPU 不高,但用户说卡,怎么办?
优先看 Waiting 和 I/O。很多卡顿不是“算不动”,而是“等太久”。
Q2:内存很大但没有崩溃,要不要优化?
看是否影响系统压力与用户体验。若无缺页、无抖动,可能是可接受缓存策略。
Q3:调用栈看不懂怎么办?
先解决符号问题,再从“自家模块”逆向追踪。不要一上来盯系统库。
10. 结语
WPR 的难点不在工具本身,而在分析顺序。
你可以用一句话总结本文:
先定时间窗,后定主瓶颈;先到进程线程,再进调用栈;每一步都要可对比、可验证。
如果你愿意,我后续可以再写一篇“WPR + WPA 实操清单版”,按按钮路径把每个视图怎么点、怎么过滤、怎么导出报告一步步展开。