
刚开始接触性能分析时我一直把任务管理器当成主力工具看到 CPU 飙高知道某个进程有问题但再往下就没有头绪了。后来用上 WindowsPerformanceToolkit才发现真正能回答“为什么高、高在哪、是不是某一行代码”的是它里面的 wpr 和 wpa 这对组合。wpr 负责录制系统事件wpa 负责把录制结果变成可交互的图表、表格和调用栈。这套工具并不复杂难的是第一次不知道从哪里入手。这篇文章我就按自己实际排查流程来写从装好工具到定位热点函数全程用 CPU 分析做主线最后再把权限、符号、文件体积这些坑一并说清楚。1. 先把工具链装明白WPT到底装了什么装完去哪找1.1 不要和任务管理器、性能监视器搞混很多人第一次接触 Windows 性能工具时会先碰“性能监视器”Perfmon它擅长看计数器比如 CPU 使用率、内存可用量随时间的变化。任务管理器则是实时快照打开一瞬间看到的是当前状态问题过去就过去了。WindowsPerformanceToolkit 的核心是 ETWEvent Tracing for Windows它做的是“有记录的观测”在你指定的时间段里把进程调度、CPU 采样、磁盘读写、网络收发等事件一条条写到 ETL 文件里。事后再用 WPA 打开这个文件你可以拖动时间轴、缩放视图、逐层展开函数栈相当于把一个曾经发生过的性能现场完整回放。我用 WPT 最多的是两类场景一类是高 CPU 问题另一类是程序卡顿或 I/O 异常。尤其是那种“跑一两个小时才出现一次”的偶发问题开着任务管理器等根本没有意义改成 WPT 提前开始录制问题复现后停止录制剩下的就是拿着 ETL 文件慢慢分析。和 Visual Studio 自带的 Performance Profiler 相比WPT 对系统全局的观测能力更强不要求你的程序一定正在调试器中运行也不会因为附加调试器而明显改变程序行为。1.2 安装勾选项和PATH路径WindowsPerformanceToolkit 不是单独分发的软件它随 Windows SDK 一起提供。安装 SDK 时在功能列表里找到“Windows Performance Toolkit”并勾选。很多人装 Visual Studio 的时候顺手装过 Windows SDK但因为没勾这个组件最后仍然找不到 wpr、wpa 命令。安装完成后默认路径通常是C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit里面有几个主要工具wpr.exeWindows Performance Recorder负责启动、停止录制wpa.exeWindows Performance Analyzer负责打开 ETL 和分析xperf.exe更早期的跟踪工具命令风格不同功能偏底层。建议把这个目录加到你的 PATH 环境变量里省得每次敲命令都要先 cd 一大串路径。添加完成后新开一个管理员权限的命令行窗口输入wpr -?如果弹出帮助信息说明安装成功。还可以用wpr -profiles查看当前系统支持的录制配置文件列表不同 Windows 版本的内置配置名称会略有差异所以后面讲到具体 profile 时先以自己机器上列出来的为准。1.3 GUI和命令行怎么选开始菜单里其实还有一个图形版“Windows Performance Recorder”界面里能选“第一级别”“第二级别”等录制项目点“开始”就能录点“保存”就能导出 ETL。优点是直观缺点是配置多、按钮多第一次用容易眼花。我更建议命令行方式因为性能分析经常要反复录制一条wpr -start CPU -filemode命令启动复现问题后wpr -stop xxx.etl结束。这个流程可以原样写进批处理脚本以后无论给哪个机器采集行为都是确定的。WPA 目前只有图形界面不能靠命令行直接输出结论但它能打开批量生产的 ETL分析阶段再手动操作即可。2. wpr采集一次有目的的录制比乱录一小时有用2.1 先回答三个问题再决定profile启动 wpr 之前要先想清楚三件事要看 CPU 还是磁盘要录多长时间程序能不能稳定复现问题这三个问题直接决定你用什么 profile、用内存模式还是文件模式。WPR 的 profile 可以理解为一组预定义的事件集合。比如“CPU”采样配置专门抓取各线程在 CPU 上的采样点“DiskIO”和“FileIO”关注磁盘队列和文件操作“Network”收集网络流量。我们可以在一个录制会话里叠加多个 profilewpr -start CPU -start DiskIO -filemode这样既保留 CPU 热点又能看到磁盘 I/O 的变化。不过没必要一上来把所有 profile 全开事件越全ETL 文件越大也越难分析。先只录 CPU等你确认 CPU 不是瓶颈再补录磁盘或网络这才是最快路径。2.2 一条最常用的录制命令序列下面是我平时最常用的录制流程。在管理员命令行中执行wpr -start CPU -filemode这里-filemode的意思是直接把事件写入磁盘文件而不是内存循环缓冲区。不加这个参数时wpr 默认使用内存缓冲区适合短时间录制如果问题复现需要几分钟内存缓冲可能会丢事件甚至报缓冲区溢出。所以我做长时间采集一律加-filemode宁可磁盘多占点空间也不希望关键事件被丢掉。录制启动后去运行被测程序并复现问题。问题确认出现后回到命令行停止wpr -stop C:\work\cpu_high.etlwpr -stop会完成缓冲区的合并和元数据写入生成的 .etl 文件就是后续分析的基础。如果中途发现录错了或者想放弃这次录制可以用wpr -cancel它会取消当前录制且不生成最终文件避免误保存一只脏 trace。2.3 录制时长里的取舍一次录制多长时间没有固定答案。如果你能精准复现问题30 秒到 1 分钟足够如果是偶发问题可能需要 10 到 30 分钟。时间越长文件体积和后期分析的定位成本就越高。我的建议是先做一次 30 秒的“快速测试”确认采集流程能跑通、wpa 能打开、符号能解析然后再做正式录制。很多人一上来就录半小时结果 ETL 文件好几个 GBWPA 打开时慢得像幻灯片反而耽误事。如果目标是分析高 CPU 程序通常不用等太久。程序起来后热度不会瞬间消失你完全可以先跑几十秒等 CPU 曲线稳定后再录 1 分钟。这一分钟里WPA 采样点数足够定位到模块和函数级别了。片段越短越容易把某段热点代码和具体时间段对应起来。3. WPA读trace不是拉一堆图表而是先问自己想看什么3.1 打开ETL和初始布局把 ETL 文件拖到 wpa.exe 上或者直接执行wpa.exe C:\work\cpu_high.etlWPA 的界面第一次打开时有点密左侧是“Graph Explorer”用于选择分析图表中间是分析图下方或右侧是数据表窗口。刚打开时不一定自动加载符号页面会先显示图形和进程级数据。WPA 里最常用的一张图是“CPU Usage (Sampled)”。它表示一段时间内 CPU 使用率的变化底层是通过固定频率采样各 CPU 上正在运行的线程得到的不是每个线程自己上报的耗时统计因此开销很低对目标程序的影响可以忽略不计。只要你看的是高 CPU 问题优先从这里入手基本不会错。3.2 用图表缩小时间窗整条 trace 可能是几十秒也可能是几十分钟。如果直接看全局表格数据量大列又多很难聚焦。正确做法是先看总体的 CPU 趋势图找到占用最高的那个时间峰然后用鼠标框选这个区间右键选择“Zoom to Selection”。这一步会把分析窗口缩小到问题最集中的时间段后续表格里的数据也随之收缩排序起来干净很多。我经常看到有人忽略这一步拿着整段几十秒的数据去排序结果一个进程里展开出几百万行事件根本没法看。缩小时间窗不是说只分析一小段而是把注意力放到症状最突出的地方。比如某段程序从 10 秒开始 CPU 飙升到 20 秒恢复正常那 10 到 20 秒之间的采样点才是有效证据前后那些正常负载反而是噪音。3.3 数据表不是Excel表而是可以展开的“树”选中“CPU Usage (Sampled)”图后下方会出现对应的数据表。WPA 的数据表自带分组和聚合逻辑很像 Excel 透视表但操作习惯不一样。默认情况下表格按进程聚合能看到每个进程的采样占比。点某一行旁边的箭头可以展开到模块级别再展开就看到函数和调用栈。这里要把列配置理解透。最关键的几个字段Process进程名Module所属模块通常是 exe 或 dllStack采样到的调用栈展开后一层一层往上Count采样次数CPU Usage 相关百分比列表示采样点占比等价于 CPU 占用近似值。表格默认排序可能是按发生时间需要手动改成按 CPU 占用降序才能最快找到热点。右键列名可以添加/删除列也可以保存当前视图方案下次打开同样类型 trace 时自动套用。这张表的另一个价值是可以右键筛选比如你只关心 App.exe就 Filter Selection 选中它其余进程全部隐藏排查时非常清爽。4. 让符号和PDB替你说话4.1 一堆十六进制地址等于没法定位第一次用 WPA 展开调用栈时你可能会看到大量形如App.exe0x2f1a的节点。有模块名有地址偏移但就是没有函数名。这种情况不是因为 WPA 坏了而是因为缺少符号文件。系统 DLL 需要微软符号你自己程序的 exe/dll 需要对应的 PDB 文件。没有符号时WPA 只能告诉你“这个地址落在哪个模块里”无法翻译成可读的函数名。所以分析自己程序的 trace一定要保留 Release 版本生成的 PDB。如果你用 C编译时默认会生成 PDB如果是 C#WPA 对托管调用栈的解析能力有限可能需要额外配置但至少也要保证系统符号可用。对于复杂问题没有 PDB 基本等于白录。4.2 设置符号路径微软的公共符号服务器地址是https://msdl.microsoft.com/download/symbols在启动 WPA 之前设置环境变量set _NT_SYMBOL_PATHsrv*C:\Symbols*https://msdl.microsoft.com/download/symbols这个变量会在 WPA 启动时读取符号文件会按需下载并缓存在本地C:\Symbols。如果你在自己的程序目录下还有 PDB可以用分号把本地路径放进去例如set _NT_SYMBOL_PATHsrv*C:\Symbols*https://msdl.microsoft.com/download/symbols;D:\myapp\symbols建议把这一句写进启动脚本或者直接加到系统环境变量里。要是 WPA 已经打开了一般可以在 Trace 相关的菜单里找到“Load Symbols”重新触发一次符号加载但更省心的是改完环境变量后重启 WPA。第一次加载符号通常比较慢微软符号服务器上文件很多耐心等一会儿状态栏会有加载进度提示。4.3 如何确认符号已经生效最简单的验证方式是看表格里的 Stack 列。如果函数名列变成类似App.exe!ComputeData的形式说明符号解析成功。如果还是App.exe0x1a2b就要从四方面排查。一是 PDB 和 exe 是否真的匹配。程序重新编译过旧 PDB 就失效了别只盯着文件名看。二是符号路径是否设置正确本地不存在时网络符号服务器能不能连通。三是 WPA 是否在加载完成后调用过刷新。四是 Release 版可能对函数做了内联部分小函数不会单独出现在栈上这是编译器优化造成的正常现象不是 WPA 的问题。实际定位时哪怕是“模块加偏移”也还有救但效率会大打折扣。你在研讨问题时能直接说出函数名和不能完全是两种节奏。所以我在项目里都会要求发布产物里单独保留 PDB并用时间戳归档后续任何一次线上 trace 分析都能找到匹配版本。5. 实战案例排查一个高CPU的Windows服务5.1 现场还原有一个后台服务进程 MySvc.exe平时 CPU 只占几个百分点但每天下午某个时间会突然冲到 80% 上下持续几分钟后恢复。任务管理器只能看到 CPU 曲线无法解释“谁在占用”。我采用 WPT 做了现场录制。使用管理员 PowerShell先启动录制wpr -start CPU -filemode然后让运维同学触发一次业务操作来复现问题。等待 CPU 飙高后记录当前时间大约 60 秒后停止wpr -stop C:\work\mysvc_highcpu.etl整个录制过程对业务几乎无感-filemode也允许长时间采集。ETL 文件生成后下一步进入 WPA 分析。5.2 WPA里我按这个顺序看打开 ETL 后我的操作顺序固定为四步。第一步先看“CPU Usage (Sampled)”总图。此时能明显看到一段冲高的波形。框选出冲高区间右键缩放避免无关时间段干扰分析。第二步查看数据表并按进程排序。确认 80% 的占用集中在 MySvc.exe而不是别的进程。这一步看似多余但很多时候高温并不是目标进程引起的可能它只是在等待某个外部组件结束后继续干活用“百叶窗效应”误导我们。第三步展开 MySvc.exe 这行按模块聚合。很快能看到热点集中在 MySvc.dll 或某几个 dll 里。第四步展开热点模块再按 Stack 分组。这时不要急于点最上面的函数而是先看 Inclusive 类的占比。比如模块下某个调用栈占了 60% 的采样持续时间是 40 秒那就说明有 40 秒的 CPU 时间都花在这条调用路径上。顺着这个 Stack 逐层展开最终会看到一个可读的函数名比如Parser::Tokenize。5.3 不要只看堆栈顶部第一次做这类分析的人很容易盯着叶子函数不放。比如最终看到的是memcpy就以为是内存拷贝性能差。其实不然真正的开销通常来自栈顶之上的调用者。它可能因为字符串拼接、临时对象构造、错误地按字节处理宽字符导致 memcpy 被调用了几百万次。所以分析时一定要同时看 Inclusive 和 ExclusiveExclusive当前函数自身消耗的时间不含子调用Inclusive当前函数包含所有子调用的总时间。按 Inclusive 排序能识别出真正值得怀疑的调用路径按 Exclusive 排序能看到具体指令级的消耗点。两者结合才能判断是该优化上层算法还是优化底层实现。那次案例最终定位到一段正则替换逻辑它在循环里反复编译同一个模式积少成多把 CPU 吃满了。从 WPA 里看到热点栈后再回代码里对照整个定位过程不过半小时。6. 这些坑我都踩过权限、符号和录制中断6.1 管理员权限不足时wpr启动可能“假成功”WPR 触及系统内核事件默认必须管理员权限。很多新手在普通命令行窗口里执行wpr -start CPU命令没有报错看起来启动成功但其实关键的内核事件没抓到。等到停止录制打开 ETL桌面版数据显示不全才意识到白录了。这个坑的识别方法很简单启动前确认 PowerShell 或 CMD 窗口标题栏里有“管理员”字样或者用whoami /groups检查当前令牌中是否有高完整性级别。如果依赖计划任务或 CI 机器自动采集同样要把任务设置成“使用最高权限运行”。如果权限不够但又要远程给没有管理员权限的机器采集可以配合 Windows 性能记录器的服务端模式或按需提升权限但这类场景比较复杂。普通开发者在本机分析直接以管理员身份运行命令行就是最稳妥的。6.2 filemode和内存模式选错事件会丢失WPR 有两种缓冲模式。默认内存模式把事件先写进内存缓冲区速度很快但缓冲区容量有限。录制时间过长、事件量过大时要么丢事件要么触发错误。filemode 模式则持续把事件写入磁盘文件更适合长时间任务。但 filemode 也不是没有代价。高频事件会在录制期间持续产生磁盘写入如果被测程序本身在做密集磁盘 I/O录制本身可能会加剧磁盘压力。碰到这种情况可以把录制时间压缩到最短或者选择更精简的 profile只录必要的事件类别别把整个系统所有事件都拉进来。我一般这样选问题能在 1 分钟内复现的用内存模式超过 1 分钟不确定何时发生的用 filemode。还有一个折中方案是先用 GUI 版把缓冲区大小调大不过大多数场景下 filemode 更省心。6.3 符号加载慢或加载不出来内网环境最常遇到符号加载问题。WPA 默认去微软符号服务器下载如果公司网络不允许访问外部地址或者网络很慢符号加载会卡很长时间最后只有系统模块的地址看不到全局函数名。解决思路是提前准备符号。你可以找一台能访问外网的机器把_NT_SYMBOL_PATH指向本地共享目录预先下载常用符号再把整份目录拷贝到内网分析机器上。分析了多少模块就补多少符号不必一次下载全套 Windows 符号那体积非常大。如果实在没有符号WPA 里至少还能看到模块名和偏移。你可以用link /dump /disasm等工具从 PDB 映射偏移但那是数据分析的下下策。更实际的做法是把性能问题先定位到模块层面再回到代码里用二分法排查。6.4 ETL文件打不开、白屏或数据为空遇到过几种情况录制时间太短只录了不到 1 秒ETL 里几乎没有采样点WPA 打开后白屏停止录制时没有使用管理员权限ETL 元数据不完整用旧版本 WPA 打开新版工具生成的 trace出现兼容性问题wpr profile 被系统更新改变复用旧脚本却加载了不存在的配置。最直接的解决办法是重录。因为录制操作本身不复杂重录比在 WPA 里折腾不完整数据划算得多。同时建议把 wpr 的命令脚本和生成的 ETL 文件名都带上日期方便回溯。如果 WPA 打不开可以试着把 ETL 文件用 xperf 工具重新导出事件束但这一步比较底层日常不推荐。7. 还可以这么用多trace对比、文件I/O和内存7.1 把优化前和优化后的trace放在一起看性能优化最怕“感觉变快了”。如果你用 WPT 录制了优化前的 trace优化后最好用同样的 profile、同样的业务操作、同样的录制时长再录一份。两份 trace 都放进 WPA窗口并列摆放缩放到相同的时间长度直接对比同一进程或同一函数的 CPU 占比。WPA 本身能把多个 trace 同时打开但图表默认不会自动对齐时间轴。我实际操作时会先用“打开新分析”把两个文件分别加载再把两个 CPU 图拖到同一个布局里。如果不喜欢手工对比也可以先把数据表导出成 CSV 或通过“导出数据”功能拉出关键列再用脚本对比采样次数和函数热点。这个流程能让你在代码评审时拿出实打实的数据而不是“感觉好像快了一点”。7.2 用DiskIO和FileIO分析程序的隐形瓶颈CPU 高不一定只是计算问题也很可能是程序在疯狂做小文件读写导致 CPU 被系统调用和文件系统驱动吃掉。录制时用wpr -start CPU -start DiskIO -start FileIO -filemodeWPA 里就能看到“Disk Usage”和“File I/O”相关的图。重点关注哪个进程产生的 I/O 最多、文件路径集中在哪、平均 I/O 大小是不是过小。大量 4KB 小写入对磁盘是非常不友好的访问模式即使总数据量不大也会让磁盘队列爆满。排查这类问题数据表里照样按进程聚合然后展开文件路径。看到某条路径在短时间内产生了成百上千次 I/O通常就是代码里循环 flush、逐字节写文件或者重复打开/关闭句柄所致。这部分分析和 CPU 分析思路一致只是看的数据源换成 I/O 事件。7.3 内存分析不是WPT的主场但也不是不能看WPT 在 CPU 和 I/O 上的能力明显强于内存分析。如果目标是查堆内存泄漏我通常建议用 WPA 里的“Virtual Memory”或专门的 .NET 内存工具比如 Visual Studio 的 Diagnostic Tools对于 C 可以用 heap profiler。WPT 能做的是看进程的提交内存Commit和虚拟内存变化趋势这可以帮助你判断一个进程是不是存在持续内存增长。如果你在 WPA 里看到某进程的虚拟内存曲线一路向上但 CPU 并不高那问题方向就转向内存分配模式。再用 ETW 的堆跟踪 profile 去录制一段时间就能看到分配栈。不过堆事件开销比较大录制时间要比 CPU 采样短一些。WPT 适合做“方向判断”细节确认还是得配合语言特定的性能工具。我个人的习惯是发现性能问题先用 WPT 做一遍全局采样锁定时间、进程、模块和调用栈然后再决定要不要上更重的分析工具。这套组合拳足够应对绝大多数 Windows 程序分析场景。有一点始终别忘了WPT 记录的采样事件虽然只是瞬间快照但大量快照放在一起就是最诚实的证据。你甚至可以把录制脚本固定成一个项目工具以后每个需要优化的程序都先来一发 trace再讨论优化方案。