Windows Performance Analyzer 简称 WPA,是 Windwos 系统中的一个性能分析工具,主要原理是通过记录 Windows 本身的事件 ETW(Event Tracing for Windows),可以将内核或者应用程序定义的事件记录到日志文件中。WAP 可以通过可视化的方式展示 ETW。
性能分析包含了两部分,一是通过 WPE 记录 ETW,二是通过 WPA 分析 ETW。
ETW记录
安装完对应工具后,可以使用 Windows Performance Recoder 来记录系统的事件。开始记录时根据需要勾选需要记录的项,First level triage 建议去掉勾选,这样能记录到更多信息。这里还有个小技巧,通过 Ctrl + Alt + Win + x 快捷键在记录的过程中插入自定义的信息,这样方便记录问题发生的时间。

WPA 分析
整体布局
整体界面如下,我这里简单分为两部分,左边部分为可用事件选择区域,右边部分为事件详情。可以通过拖拽的方式将左侧的可用事件放入事件详情中,在事件详情中可以看到事件的图表形式,方便分析。

堆栈配置
对于性能问题定位,最理想的是可以定位具体的函数,这个WPA也是支持的。可以通过下面的方式导入程序的 PDB,先配置,后使用。

卡顿分析例子
1)首先选择要分析的进程
只要是带有进程信息的事件类型,都可以通过下面的方式过滤出需要分析的进程。

这里在 CPU 使用率的采样中过滤出了自己的测试进行。从这里一样可以看到存在 4 个 CPU 使用的高峰,以及一个基本不使用 CPU 的时间段。(从上帝视角来说,这几个时间段就是程序卡顿的时间段,不过每种卡顿的原因各不相同)

2)定位到主线程
其实大部分导致卡顿的原因都是主线程繁忙不能正常响应 UI 事件,所以第一步就是需要定位到主线程。使用CPU Usage(Precise)图表,可以看到New Thread Id 列 6148 为主线程,因为从调用堆栈中能看到进程的入口。

如果没有看到 New Thread Id 的配置,可以通过下面方式筛选出来。


3)观察线程的生命周期
每个图标可以切换展示的模式,在 CPU Usage(Precise)图标中可以选择 Timeline by Process,Thread 模式,观察线程的生命周期,结合上面找到的主线id 6148,可以看到图表中黄色的为主线程的生命周期。

对比 CPU 使用率上升时间点和线程使用情况的时间点,可以发现。这几个地方主线程都是一直处于忙碌状态。

4)通过缩小范围进一步确认
可以通过选择时间段进行 Zoom。

5)定位到对应的函数
由于在分析的过程中加载了 PDB,因此这个可以定位到具体的函数。
- 第一段:卡顿的时间点,定位到了
simulateComputeIntensiveTask函数,是一个计算密集性的函数。

- 第二段:模拟的是一个 I/O 密集性的函数,如果打开
File I/O的采集数据图标,可以定位到具体的读写操作以及对应的文件信息。

- 第三段:
simulateLockContention,代码如下,频繁的加锁。本来是想模拟锁的消耗,结果从分析结果来看,锁的开销相对来说比较小,占比在0.03%,主要还是在计算。
// Simulate lock contention: frequent lock acquire/release in main thread
const int lockOperations = 5000000; // Increase to 5 million lock operations
// Remove processEvents() to create real blocking
for (int i = 0; i < lockOperations; ++i) {
{
QMutexLocker locker(&m_testMutex);
// Do more work inside the lock to increase contention
volatile int dummy = 0;
for (int j = 0; j < 500; ++j) { // Increase inner loop
dummy += j * j;
dummy = dummy % 1000;
}
}
// Add some work outside the lock too
volatile int temp = i * i;
temp = temp % 1000;
}

- 第四段:
simulateMemoryIntensiveTask,该函数的主要代码如下,是一个反复申请内存的函数。结合着VirtualAlloc Commit LifeTimes图标,可以看到具体申请内存随时间变化的情况。

- 在来看一个特殊的时间段,表现是 cpu 使用率为 0,该时间段主线程基本上没有活动。

-
- 从
CPU Usage(Sampled)采样来看,看不到任何堆栈。这时候在看下CPU Usage(Precise)能看到主线程的一个堆栈使用情况,simulateThreadLockContention,该函数是模拟了两个线程枪锁的情况。
- 从

-
- 结合线程的生命周期来看,该线程等待了2.8s左右(代码中等待了次线程获取了3s的锁)。
