Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 系统与高性能 / Systems & Performance

性能工程 / Performance Engineering

1. 性能工程全景:从现象到证据 / Performance Engineering Overview

2. 基准测试:把变快变成可信实验 / Benchmarking as Evidence

3. 系统级追踪:用时间线看等待、调度与 I/O / System Tracing

4. CPU、内存、Cache 与 NUMA:把热点解释到硬件行为 / CPU, Memory, Cache and NUMA

5. GPU、Roofline 与异构瓶颈 / GPU, Roofline and Heterogeneous Bottlenecks

6. 优化闭环:从假设、改动到回归守护 / Optimization, Regression and Production

本页目录

系统级追踪:用时间线看等待、调度与 I/O / System Tracing ​

很多慢问题不是 CPU 算得慢,而是工作没有被推进。

线程可能在等锁,等磁盘,等网络,等 GPU,等另一个线程唤醒,或者明明已经 Ready 却没有被调度器安排到 CPU 上。CPU 采样火焰图只能看到“正在运行时执行了什么”,看不全这些等待。

系统级追踪的价值,就是把事件放回时间线上。

1. 火焰图看宽度,时间线看顺序 ​

火焰图回答的是聚合问题:

text
CPU 样本主要落在哪些调用路径?
1

时间线回答的是因果问题:

text
哪个线程先做了什么?
它什么时候停下?
为什么停下?
谁把它唤醒?
I/O、GPU、网络和 CPU 是否互相等待?
1
2
3
4
5

示意:

text
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........]
1
2
3
4
5
6
7

如果只看 CPU 火焰图,UI Thread 的等待可能几乎不可见。

但用户感受到的卡顿正是在等待里。

2. Windows:WPR 采集,WPA 分析 ​

Windows 性能分析的核心组合是 WPR 和 WPA。

WPR 负责记录。

WPA 负责分析。

WPR 采集的是 ETW 事件。ETW 是 Windows 的系统事件追踪机制,可以记录内核调度、CPU、磁盘、文件、网络、进程线程、GPU、堆分配以及应用自定义事件。

WPA 打开 ETL 文件,把这些事件变成时间线、表格和图。

基本流程:

text
选择 Profile
  |
  v
开始记录
  |
  v
复现慢路径
  |
  v
保存 ETL
  |
  v
WPA 打开
  |
  v
框选慢区间
  |
  v
按线程、进程、调用栈和资源归因
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

实际使用时先别急着看所有图。

先框选用户感受到慢的时间段,再看这段里线程状态。

常看视图包括:

  • CPU Usage:进程和线程消耗 CPU 的情况;
  • CPU Usage Precise:更精确地分析调度、Ready、Running;
  • Thread Activity:线程运行、等待、唤醒;
  • Disk Usage:磁盘活动;
  • File I/O:文件路径、读写大小、调用栈;
  • Generic Events:应用自定义 ETW 事件;
  • GPU 相关视图:图形或计算队列活动。

3. WPA 分析卡顿的顺序 ​

假设桌面程序打开项目卡了 8 秒。

不要先猜“解析算法慢”。

可以这样走:

text
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 同步点
1
2
3
4
5
6
7
8
9
10
11
12

这套顺序的好处是避免过早进入某个工具局部。

如果 UI 线程 6 秒都在等文件 I/O,你重写解析循环可能没有明显收益。

如果 UI 线程一直 Running 且 CPU 调用栈集中在解析函数,才进入 CPU Profiling 或源码层优化。

4. Thread State:Running、Ready、Waiting ​

理解线程状态很关键。

Running 表示线程正在 CPU 上执行。

Ready 表示线程可以运行,但暂时没有拿到 CPU。

Waiting 表示线程主动等待某个事件,例如锁、I/O、定时器、条件变量或 GPU 完成。

text
Thread
  |
  +-- Running: 正在执行指令
  |
  +-- Ready: 想运行,但 CPU 没轮到它
  |
  +-- Waiting: 条件不满足,不能继续
1
2
3
4
5
6
7

Ready 时间高,常见原因是 CPU 饱和、线程过多、优先级问题、容器配额或系统被其他进程抢占。

Waiting 时间高,常见原因是锁、I/O、RPC、事件、睡眠、GPU 同步和下游任务。

二者方向完全不同。

Ready 高时增加线程通常更糟。

Waiting 高时盲目优化 CPU 指令也可能无效。

5. Off-CPU 分析 ​

On-CPU 是线程正在执行的时间。

Off-CPU 是线程没有执行的时间。

慢请求经常主要由 Off-CPU 组成:

text
wall time = on-cpu time + off-cpu time
1

Off-CPU 要继续分:

  • 等锁;
  • 等条件变量;
  • 等磁盘;
  • 等网络;
  • 等 GPU;
  • 等 Timer;
  • Ready 但没被调度;
  • 进程被挂起;
  • 容器或虚拟化限制。

Windows 用 WPA 看 Thread Activity 和等待原因。

Linux 可以用 perf sched、ftrace、Perfetto 或 bpftrace 追踪调度和阻塞事件。

6. Linux:perf、ftrace、Perfetto ​

Linux 下 perf 不只会做 CPU 火焰图,它也能记录 tracepoint 和调度事件。

常见起步:

bash
perf stat ./app
perf record -g -- ./app
perf report
1
2
3

如果怀疑调度问题:

bash
perf sched record -- ./app
perf sched latency
1
2

如果要看更丰富的系统时间线,可以使用 Perfetto 或 ftrace。

Perfetto 的优势是把调度、CPU 频率、系统调用、内存、应用事件和 CPU Profile 放到统一时间线里,并支持 SQL 查询。

这对复杂客户端、Android、嵌入式 Linux 和多进程系统很有用。

7. bpftrace:临时追问系统 ​

bpftrace 适合提出很具体的问题。

例如:

text
哪些进程在调用 openat?
某个函数参数分布是什么?
谁在频繁触发 page fault?
哪个线程等待 futex 最多?
1
2
3
4

它的价值在于不用改代码、不用重启复杂系统,就能临时挂探针。

示例形态:

bash
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'
1

这条命令在 openat 系统调用入口计数,并按进程名聚合。

使用 bpftrace 要注意权限、内核版本、符号可见性和开销。它适合短时间、有边界地回答问题,不适合作为永久业务逻辑。

8. 应用自定义事件 ​

系统工具只能看到系统事件。

它不知道你的“解析网格”“装配矩阵”“生成索引”“提交渲染命令”分别是什么意思。

所以复杂项目应该给关键阶段打点。

text
ProjectOpen
  |
  +-- ReadFile
  +-- ParseEntities
  +-- BuildTopology
  +-- AllocateBuffers
  +-- UploadToGPU
  +-- FirstRender
1
2
3
4
5
6
7
8

Windows 可用 ETW Provider。

Perfetto 可用 Track Event SDK。

CUDA 可用 NVTX 标记业务阶段,让 Nsight Systems 能把 CPU 代码和 GPU 工作对应起来。

有了业务事件,时间线才不只是一堆线程和系统调用,而能对应真实项目流程。

9. I/O 不是一个单点 ​

文件慢不等于磁盘慢。

一次文件读取可能经过:

text
应用层调用
  |
  v
运行库缓冲
  |
  v
系统调用
  |
  v
文件系统
  |
  v
页缓存
  |
  v
块设备队列
  |
  v
存储设备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

要观察:

  • 读写次数;
  • 单次大小;
  • 是否顺序;
  • 是否同步等待;
  • 是否命中页缓存;
  • 是否有元数据开销;
  • 是否被杀毒、索引、压缩或网络盘影响;
  • I/O 发生在哪个线程;
  • I/O 是否阻塞关键路径。

很多性能问题来自小块同步 I/O。解决方向可能是批量读取、异步读取、预读、内存映射、减少元数据查询或把 I/O 移出主线程。

10. 锁和唤醒关系 ​

锁等待要看持有者。

只知道“线程 A 等锁 200 ms”还不够。

你需要知道:

  • 谁持有锁;
  • 持锁时做了什么;
  • 等待队列里有多少线程;
  • 临界区是否包含 I/O;
  • 是否有优先级反转;
  • 锁是否保护了过大的全局状态;
  • 唤醒后线程是否马上 Running,还是长时间 Ready。

典型问题:

text
Worker 1 持有全局锁
  |
  +-- 在锁内写日志
  +-- 日志触发文件 I/O
  +-- UI Thread 等同一把锁
  +-- 用户看到卡顿
1
2
3
4
5
6

此时优化日志格式、CPU 循环或线程数量都不是核心。真正问题是锁边界错了。

11. 时间线报告模板 ​

text
现象:
  点击“打开工程”后界面冻结 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,并阻塞主线程等待。

下一步:
  做批量读取实验,同时检查峰值内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

注意这里没有直接跳到“改成多线程”。

因为证据显示主问题是小块同步 I/O 和线程等待。

12. 本篇总结 ​

系统级追踪适合回答“慢发生在什么时间顺序里”。

WPR/WPA 是 Windows C++ 项目的第一把大锤,尤其适合桌面卡顿、I/O、线程等待和整机资源问题。

Perfetto、perf sched、ftrace 和 bpftrace 则是 Linux/Android/嵌入式系统里观察调度、系统调用和跨进程事件的重要工具。

先看时间线,再看热点。这个顺序能少走很多弯路。

最后更新于:

Pager
上一篇2. 基准测试:把变快变成可信实验 / Benchmarking as Evidence
下一篇4. CPU、内存、Cache 与 NUMA:把热点解释到硬件行为 / CPU, Memory, Cache and NUMA

持续记录,持续成长

Copyright © Tidenflow