文章

用 WPR 分析性能问题该从哪看起:从 CPU、内存到耗时路径的实战路线

一篇从入门到进阶的 WPR 使用指南,讲清楚性能分析应先看哪些指标、如何针对 CPU 高、内存多、进程耗时长定位根因,并给出可复用的排查流程

用 WPR 分析性能问题该从哪看起:从 CPU、内存到耗时路径的实战路线

很多同学第一次用 WPR(Windows Performance Recorder)会遇到同一个问题:

轨迹录出来了,图也很多,但不知道应该先看哪一个指标,更不知道怎么从“现象”走到“根因”。

这篇文章我给你一个可执行的分析顺序,并把常见的三类问题拆开讲:

  1. CPU 占用高怎么查;
  2. 内存占用大怎么查;
  3. 进程耗时长(卡顿、慢请求、启动慢)怎么查。

目标是:让你不再“看图发呆”,而是有一条从浅入深的路径。


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)

先回答三件事:

  1. 是单核打满还是多核普遍高;
  2. 是单进程高还是系统普遍高;
  3. 峰值是持续高还是脉冲高。

4.2 第二个观察面:按进程/线程分解

看“哪个线程”贡献了主要 CPU:

  • 若是业务线程:大概率是算法/循环/解析/序列化等热点;
  • 若是 GC 线程:需要看分配速率与对象生命周期;
  • 若是系统线程或中断:可能是驱动、网络包风暴、设备问题。

4.3 第三个观察面:调用栈与符号

在热点线程上看栈,重点找:

  • 自家模块函数是否在热点路径;
  • 是否大量时间停在某个库函数(压缩、加密、正则、JSON);
  • 是否存在重复计算或过深递归。

4.4 常见误区

  • 误区 1:CPU 高就一定是“慢”——如果用户路径不受影响,可能只是后台任务;
  • 误区 2:只看 Top 函数——要看调用链与调用次数;
  • 误区 3:忽略上下文切换——线程切得太频繁也会“看起来 CPU 高但业务没推进”。

5. 场景二:内存多,该从哪些指标开始?

“内存多”至少分三类:

  1. 正常缓存增长;
  2. 暂时峰值(可回落);
  3. 持续增长(疑似泄漏)。

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 排查模板(建议收藏)

每次分析都按下面填:

  1. 问题定义:动作、时长、影响范围;
  2. 时间窗:trace 中起止时间;
  3. 主瓶颈判定:CPU / Memory / I/O / Lock;
  4. Top 进程:资源占比与峰值时刻;
  5. Top 线程:Running/Waiting 比例;
  6. 热点栈:函数链路(不少于 3 层);
  7. 根因假设:最多 2 个;
  8. 验证动作:改配置、降采样、替代实现、AB 对比;
  9. 结论与收益:优化前后指标对比。

这样做的好处是:团队复盘时不再“各说各话”。


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 实操清单版”,按按钮路径把每个视图怎么点、怎么过滤、怎么导出报告一步步展开。

本文由作者按照 CC BY 4.0 进行授权