系统级追踪:用时间线看等待、调度与 I/O / System Tracing
很多慢问题不是 CPU 算得慢,而是工作没有被推进。
线程可能在等锁,等磁盘,等网络,等 GPU,等另一个线程唤醒,或者明明已经 Ready 却没有被调度器安排到 CPU 上。CPU 采样火焰图只能看到“正在运行时执行了什么”,看不全这些等待。
系统级追踪的价值,就是把事件放回时间线上。
1. 火焰图看宽度,时间线看顺序
火焰图回答的是聚合问题:
CPU 样本主要落在哪些调用路径?时间线回答的是因果问题:
哪个线程先做了什么?
它什么时候停下?
为什么停下?
谁把它唤醒?
I/O、GPU、网络和 CPU 是否互相等待?示意:
time --------------------------------------------------------->
UI Thread: [parse][wait file........][render][wait gpu...]
Worker 1: [read file......][decode][signal]
Worker 2: [idle.................][work....]
GPU Queue: [copy][kernel][gap.........][kernel]
Disk: [read blocks........]如果只看 CPU 火焰图,UI Thread 的等待可能几乎不可见。
但用户感受到的卡顿正是在等待里。
2. Windows:WPR 采集,WPA 分析
Windows 性能分析的核心组合是 WPR 和 WPA。
WPR 负责记录。
WPA 负责分析。
WPR 采集的是 ETW 事件。ETW 是 Windows 的系统事件追踪机制,可以记录内核调度、CPU、磁盘、文件、网络、进程线程、GPU、堆分配以及应用自定义事件。
WPA 打开 ETL 文件,把这些事件变成时间线、表格和图。
基本流程:
选择 Profile
|
v
开始记录
|
v
复现慢路径
|
v
保存 ETL
|
v
WPA 打开
|
v
框选慢区间
|
v
按线程、进程、调用栈和资源归因实际使用时先别急着看所有图。
先框选用户感受到慢的时间段,再看这段里线程状态。
常看视图包括:
- CPU Usage:进程和线程消耗 CPU 的情况;
- CPU Usage Precise:更精确地分析调度、Ready、Running;
- Thread Activity:线程运行、等待、唤醒;
- Disk Usage:磁盘活动;
- File I/O:文件路径、读写大小、调用栈;
- Generic Events:应用自定义 ETW 事件;
- GPU 相关视图:图形或计算队列活动。
3. WPA 分析卡顿的顺序
假设桌面程序打开项目卡了 8 秒。
不要先猜“解析算法慢”。
可以这样走:
1. 在 WPR 中选择 CPU、Disk、File、GPU、Memory 相关 Profile
2. 点击 Start
3. 复现打开项目
4. 保存 ETL
5. WPA 打开 ETL
6. 找到卡顿的 8 秒
7. 框选这 8 秒
8. 看 UI 线程状态
9. 如果 Running:再看 CPU 调用栈
10. 如果 Waiting:看等待原因和唤醒者
11. 如果 File I/O 高:看路径、大小、次数和调用栈
12. 如果 GPU 队列空洞:看 CPU/GPU 同步点这套顺序的好处是避免过早进入某个工具局部。
如果 UI 线程 6 秒都在等文件 I/O,你重写解析循环可能没有明显收益。
如果 UI 线程一直 Running 且 CPU 调用栈集中在解析函数,才进入 CPU Profiling 或源码层优化。
4. Thread State:Running、Ready、Waiting
理解线程状态很关键。
Running 表示线程正在 CPU 上执行。
Ready 表示线程可以运行,但暂时没有拿到 CPU。
Waiting 表示线程主动等待某个事件,例如锁、I/O、定时器、条件变量或 GPU 完成。
Thread
|
+-- Running: 正在执行指令
|
+-- Ready: 想运行,但 CPU 没轮到它
|
+-- Waiting: 条件不满足,不能继续Ready 时间高,常见原因是 CPU 饱和、线程过多、优先级问题、容器配额或系统被其他进程抢占。
Waiting 时间高,常见原因是锁、I/O、RPC、事件、睡眠、GPU 同步和下游任务。
二者方向完全不同。
Ready 高时增加线程通常更糟。
Waiting 高时盲目优化 CPU 指令也可能无效。
5. Off-CPU 分析
On-CPU 是线程正在执行的时间。
Off-CPU 是线程没有执行的时间。
慢请求经常主要由 Off-CPU 组成:
wall time = on-cpu time + off-cpu timeOff-CPU 要继续分:
- 等锁;
- 等条件变量;
- 等磁盘;
- 等网络;
- 等 GPU;
- 等 Timer;
- Ready 但没被调度;
- 进程被挂起;
- 容器或虚拟化限制。
Windows 用 WPA 看 Thread Activity 和等待原因。
Linux 可以用 perf sched、ftrace、Perfetto 或 bpftrace 追踪调度和阻塞事件。
6. Linux:perf、ftrace、Perfetto
Linux 下 perf 不只会做 CPU 火焰图,它也能记录 tracepoint 和调度事件。
常见起步:
perf stat ./app
perf record -g -- ./app
perf report如果怀疑调度问题:
perf sched record -- ./app
perf sched latency如果要看更丰富的系统时间线,可以使用 Perfetto 或 ftrace。
Perfetto 的优势是把调度、CPU 频率、系统调用、内存、应用事件和 CPU Profile 放到统一时间线里,并支持 SQL 查询。
这对复杂客户端、Android、嵌入式 Linux 和多进程系统很有用。
7. bpftrace:临时追问系统
bpftrace 适合提出很具体的问题。
例如:
哪些进程在调用 openat?
某个函数参数分布是什么?
谁在频繁触发 page fault?
哪个线程等待 futex 最多?它的价值在于不用改代码、不用重启复杂系统,就能临时挂探针。
示例形态:
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'这条命令在 openat 系统调用入口计数,并按进程名聚合。
使用 bpftrace 要注意权限、内核版本、符号可见性和开销。它适合短时间、有边界地回答问题,不适合作为永久业务逻辑。
8. 应用自定义事件
系统工具只能看到系统事件。
它不知道你的“解析网格”“装配矩阵”“生成索引”“提交渲染命令”分别是什么意思。
所以复杂项目应该给关键阶段打点。
ProjectOpen
|
+-- ReadFile
+-- ParseEntities
+-- BuildTopology
+-- AllocateBuffers
+-- UploadToGPU
+-- FirstRenderWindows 可用 ETW Provider。
Perfetto 可用 Track Event SDK。
CUDA 可用 NVTX 标记业务阶段,让 Nsight Systems 能把 CPU 代码和 GPU 工作对应起来。
有了业务事件,时间线才不只是一堆线程和系统调用,而能对应真实项目流程。
9. I/O 不是一个单点
文件慢不等于磁盘慢。
一次文件读取可能经过:
应用层调用
|
v
运行库缓冲
|
v
系统调用
|
v
文件系统
|
v
页缓存
|
v
块设备队列
|
v
存储设备要观察:
- 读写次数;
- 单次大小;
- 是否顺序;
- 是否同步等待;
- 是否命中页缓存;
- 是否有元数据开销;
- 是否被杀毒、索引、压缩或网络盘影响;
- I/O 发生在哪个线程;
- I/O 是否阻塞关键路径。
很多性能问题来自小块同步 I/O。解决方向可能是批量读取、异步读取、预读、内存映射、减少元数据查询或把 I/O 移出主线程。
10. 锁和唤醒关系
锁等待要看持有者。
只知道“线程 A 等锁 200 ms”还不够。
你需要知道:
- 谁持有锁;
- 持锁时做了什么;
- 等待队列里有多少线程;
- 临界区是否包含 I/O;
- 是否有优先级反转;
- 锁是否保护了过大的全局状态;
- 唤醒后线程是否马上 Running,还是长时间 Ready。
典型问题:
Worker 1 持有全局锁
|
+-- 在锁内写日志
+-- 日志触发文件 I/O
+-- UI Thread 等同一把锁
+-- 用户看到卡顿此时优化日志格式、CPU 循环或线程数量都不是核心。真正问题是锁边界错了。
11. 时间线报告模板
现象:
点击“打开工程”后界面冻结 8.3 s。
采集:
WPR GeneralProfile + File I/O + CPU,复现一次,保存 ETL。
慢区间:
12.4 s 到 20.7 s。
观察:
UI 线程 6.1 s Waiting;
等待期间 Worker-3 执行同步文件读取;
File I/O 显示 48,000 次小块读取;
CPU 并非瓶颈。
假设:
属性表按实体逐条读取导致大量小 I/O,并阻塞主线程等待。
下一步:
做批量读取实验,同时检查峰值内存。注意这里没有直接跳到“改成多线程”。
因为证据显示主问题是小块同步 I/O 和线程等待。
12. 本篇总结
系统级追踪适合回答“慢发生在什么时间顺序里”。
WPR/WPA 是 Windows C++ 项目的第一把大锤,尤其适合桌面卡顿、I/O、线程等待和整机资源问题。
Perfetto、perf sched、ftrace 和 bpftrace 则是 Linux/Android/嵌入式系统里观察调度、系统调用和跨进程事件的重要工具。
先看时间线,再看热点。这个顺序能少走很多弯路。