这一章在做什么
回答两个问题:CPU 是不是瓶颈,如果是,时间花在了哪段代码上。先分清「CPU 很忙」(利用率)和「CPU 不够用、线程在排队」(饱和度),再从全系统看到每个核、每个进程和线程,最后用采样剖析把 CPU 时间落到函数上,画成火焰图。顺带讲清负载平均在 Linux 上为什么不只代表 CPU,以及忙不等于在干活:周期可能都耗在等内存上。
什么时候翻这一章
- 服务把 CPU 吃满了,想知道是哪段代码先确认是谁在用(进程、线程),再采样剖析、看火焰图。§6.5.4§6.6.13§6.7.3场景手册 →
- load average 很高,但 top 里 CPU 还有空闲Linux 的负载平均把等磁盘的线程也算进去了,先分清是哪一种负载。§6.6.1场景手册 →
- CPU 没满,请求延迟却在抖可能是线程在排队等 CPU,也可能是某一个核满了、别的核闲着。§6.6.3§6.6.16场景手册 →
- 容器或云主机里,CPU 看着不满却很慢限额先于物理 CPU 用完,饱和度要按限额算。§6.5.2§6.9.8场景手册 →
- 换了主频更高的 CPU,没快多少看 IPC:周期多半在等内存,主频再高也只是等得更快。§6.3.7§6.5.5
这一章的方法来自第 2 章(USE 方法、负载特征),剖析和火焰图第 5 章讲过应用这一侧,perf 和 BPF 工具的细节在第 13、15 章。线程不在 CPU 上的时间(等锁、等 IO)归第 5 章的 off-CPU 分析。
先用中文过一遍
利用率和饱和度:忙和不够是两回事
利用率(utilization)问的是:一段时间里,这个 CPU 有多少时间不在跑空闲线程。80% 就是十秒里有八秒在干活。饱和度(saturation)问的是:有多少线程已经准备好了,却因为 CPU 全被占着而在排队。两者的关系像收银台:收银员一直在忙是利用率,后面排着的队是饱和度。
CPU 和磁盘不同的地方是,到了 100% 也不一定出事:内核有优先级和抢占,高优先级的工作可以插队(§6.3.10)。所以看 CPU 不能只看利用率,要同时看排队:Linux 上是 vmstat 的 r 列(正在跑的加上排队的)、pidstat 的 %wait,以及 BCC 的 runqlat 量出的每次排队等了多久。还有一点:利用率要按核看。8 核机器平均 12%,可能是一个核 100%、其余全闲,这正是单线程瓶颈的样子(§6.6.3)。
负载平均:在 Linux 上不只是 CPU
负载平均(load average)是一个数:正在用资源的加上在排队的,做成指数衰减的移动平均。uptime 给出三个,分别是 1、5、15 分钟的。三个数一比,就知道负载在涨还是在落:如果 1 分钟的远小于 15 分钟的,问题可能已经过去了。
容易误读的地方有两个。第一,在 Linux 上它不只算 CPU:处于不可中断睡眠(D 状态,多半在等磁盘)的线程也算进去,所以负载 8 的机器可能 CPU 很闲、磁盘很忙。第二,「1 分钟平均」不是过去一分钟的算术平均:一个持续占满一个核的线程跑了一分钟,1 分钟的值大约才到 0.6(§6.6.1)。书里的建议是:负载平均只用来起头,别在上面花超过五分钟。要按资源分开看,用 PSI(压力停顿信息):/proc/pressure/cpu 告诉你有任务因为等 CPU 而停顿的时间比例。
剖析:采样,再叠成火焰图
想知道 CPU 时间花在哪,最省事的办法是定时采样:每秒在每个 CPU 上打断 99 次,记下当时正在跑的调用栈(§6.5.4)。用 99 而不是 100,是因为 100 Hz 可能和某些周期性活动步调一致,让样本系统性地偏向它(书里的说法是 may)。采到几千个栈之后,一条一条读不过来,就把相同的栈合并计数,画成火焰图(flame graph):每一格是一个函数,下面一格是它的调用者;格子越宽,说明它(连同它调用的函数)在越多的样本里出现。
读火焰图的规矩只有几条:横轴不是时间,从左到右按函数名排序,左右位置没有意义;顶上一层是采样那一刻正在 CPU 上的函数;先看最宽的「塔」,再看顶上又宽又平的一段(平台,plateau),那是直接烧 CPU 的地方。采样只看见在 CPU 上的时间:线程在睡、在等锁、在等 IO 时不会被采到,所以火焰图里看不到「慢在等待」的问题。
IPC:忙不等于在干活
利用率把一个 CPU 周期算作「忙」,不管这个周期是在执行指令,还是在干等内存把数据送上来。IPC(每周期指令数)把这两者分开:perf 用硬件计数器数一段时间里执行了多少条指令、过了多少个周期,一除就是 IPC。书里举的例子是:低 IPC 的负载在 0.2 上下或更低,高的能超过 1,但具体数值看处理器,最好用已知负载自己量一量(§6.3.7)。IPC 低,说明 CPU 多半在等内存,该想的是数据布局、缓存命中,换主频更高的 CPU 帮助不大;IPC 高,说明指令跑得顺,该想的是能不能少执行一些指令。注意 IPC 高不代表代码高效:一个只在寄存器里空转的循环,IPC 很高,干的却是无用功。
| 英文 | 中文 | 一句话 |
|---|---|---|
| utilization | 利用率 | 一段时间里 CPU 不在跑空闲线程的比例,要按核看 §6.3.8 术语页 |
| saturation | 饱和度 | 想上 CPU 却在排队的程度;100% 利用率之后才有 §6.3.10 术语页 |
| run queue | 运行队列 | 可以跑、在等 CPU 的线程;Linux 实际用红黑树,叫法沿用下来 §6.2.3 |
| scheduler latency | 调度延迟 | 线程从可以跑到真正跑上 CPU 等了多久,也叫运行队列延迟 §6.6.16 术语页 |
| load average | 负载平均 | 在用加在等的数量的指数衰减平均;Linux 上含 D 状态 §6.6.1 术语页 |
| PSI (pressure stall information) | 压力停顿信息 | 按 CPU、内存、IO 分开给出任务停顿的时间比例,Linux 4.20 加入 §6.6.1 术语页 |
| user time / kernel time | 用户态时间 / 内核态时间 | 两者的比例说明负载的类型:计算型几乎全是用户态 §6.3.9 |
| IPC (instructions per cycle) | 每周期指令数 | 区分「忙着执行」和「忙着等内存」,倒数是 CPI §6.3.7 术语页 |
| PMC (performance monitoring counter) | 硬件性能计数器 | 处理器里能编程计数的寄存器:周期、指令、缓存未命中 §6.4.1 术语页 |
| profiling | 剖析 | 定时采样调用栈,统计 CPU 时间花在哪些代码路径上 §6.5.4 术语页 |
| flame graph | 火焰图 | 把采到的栈合并画出来,宽度是样本数,横轴不是时间 §6.7.3 术语页 |
| hot CPU | 热核 | 别的核闲着、它一个跑满,多半是单线程或中断集中 §6.6.3 |
| steal time | 被偷走的时间 | 虚拟机里,宿主把这个虚拟 CPU 的时间给了别的租户 §6.6.2 |
方法:按什么顺序查
书里列了十一种 CPU 的方法,作者自己推荐的顺序是:性能监控、USE 方法、剖析、微基准测试、静态调优(§6.5)。落到一次排查上,本站整理成下面几步。前四步几分钟就能做完,用的都是 sysstat 和 procps 里的老工具;第五步开始要 perf 或 BPF。
- 负载在涨还是在落?
uptime看三个负载平均,cat /proc/pressure/cpu看 CPU 停顿比例。在落,说明问题可能已经过去,别在这一步停留。§6.6.1 - CPU 忙吗?每个核都忙吗?
mpstat -P ALL 1:看每个核的%usr+%sys。全都接近 100% 是整体不够;一个核满、其余闲是单线程或中断集中;虚拟机里顺便看%steal。§6.6.3 - 有没有在排队?
vmstat 1的r长期大于核数,或pidstat的%wait明显不为零,就是饱和了。要量每次等多久,用runqlat(BCC,要 root)。容器里还要按限额算。§6.5.2§6.6.16 - 谁在用?用户态还是内核态?
pidstat 1找出进程,pidstat -t -p PID 1看到线程。%system高说明时间花在系统调用或中断上,往第 5 章的系统调用分析走。§6.6.7 - 为什么在用:哪段代码?
perf record -F 99 -g采样,perf report看文本,或者画成火焰图。先看最宽的塔,再看顶上的平台。§6.5.4§6.7.3 - 周期是在执行指令还是在等内存?
perf stat看 IPC。低(比如 0.3 以下)往缓存和内存访问上查;高就去减少指令。§6.5.5 - 别漏掉的:中断和配置
mpstat的%irq、%soft,/proc/softirqs;频率调节器是不是powersave;有没有 cgroup 限额。这些在剖析里不显眼,却常常是答案。§6.5.7§6.6.18
工具速查
| 想知道 | 敲什么 | 看哪里 | 节 |
|---|---|---|---|
| 负载的趋势 | uptimecat /proc/pressure/cpu | 1、5、15 分钟三个数的走向;PSI 的 some avg10 是最近 10 秒有任务在等 CPU 的时间比例 | §6.6.1 |
| 全局利用率和排队 | vmstat 1 | r 和核数比;us + sy;wa 是 CPU 闲着而有线程在等磁盘;st 是被宿主拿走的 | §6.6.2 |
| 每个核 | mpstat -P ALL 1 | 有没有单个核 %usr + %sys 接近 100%;%soft 集中在某个核,常常是网卡中断没摊开 | §6.6.3 |
| 哪个进程、线程 | pidstat 1pidstat -t -p PID 1 | %usr、%system 分开看;%wait 是等 CPU 的时间 | §6.6.7 |
| 交互地看 | top,按 1 看每核,H 看线程 | %CPU 默认不除以核数(top 叫它 Irix 模式,按 I 切换),两个线程跑满就是 200% | §6.6.6 |
| 哪段代码在烧 CPU | perf record -F 99 -g -p PID -- sleep 30perf report --stdio | 按样本占比排的函数和调用路径;看到一串十六进制地址是缺符号 | §6.6.13 |
| 画火焰图 | perf script > out.perf,再用 FlameGraph 的两个脚本转成 SVG | 最宽的塔、顶上的平台;在浏览器里点击放大、Ctrl-F 搜索 | §6.7.3 |
| 低开销剖析(BPF) | profile-bpfcc -F 49 30 | 栈在内核里汇总,只交出计数,比 perf 写文件省;-f 输出能直接画火焰图 | §6.6.14 |
| 每次排队等了多久 | runqlat-bpfcc 10 1 | 直方图里毫秒级的桶有没有数;开销比 runqlen 大 | §6.6.16 |
| 队列有多长(便宜) | runqlen-bpfcc 10 1 | 队列长度为 0 的比例;按 99 Hz 采样,开销很小 | §6.6.17 |
| IPC | perf stat -e cycles,instructions ./prog | insn per cycle;虚拟机里可能拿不到硬件计数器 | §6.6.13 |
| 中断占了多少 | cat /proc/softirqssoftirqs-bpfcc 10 1 | 计数在不在涨、集中在哪个核;BCC 版给出每类软中断花的时间 | §6.6.18 |
| 频率是不是被压着 | cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor | 书里的例子是没调过的机器停在 powersave,频率偏低;板子上默认用哪个看 BSP 的内核配置 | §6.9.4 |
mpstat、pidstat 在 sysstat 包里;perf 在 Debian 上是 linux-perf 包;带 -bpfcc 后缀的是 Debian 的 bpfcc-tools 包里的 BCC 工具,要 root。工具名的链接到本站的工具卡。常用的几个:mpstat、pidstat、vmstat、perf、runqlat、profile。
动手:在自己的机器上试
找出一个小服务把 CPU 花在哪
下面这个程序假装在处理请求:每个请求算一次校验和,再格式化一行日志。先猜一猜:两件事里谁更费 CPU?然后用 perf 量。
// hot.c:假装是一个处理请求的服务,每个请求算一次校验和、写一行日志
#include <stdio.h>
#define NOINLINE __attribute__((noinline)) // 不让编译器内联,剖析时每个函数都看得见
NOINLINE static unsigned checksum(const unsigned char *p, int n) {
unsigned s = 0;
for (int i = 0; i < n; i++) s = s * 31 + p[i];
return s;
}
NOINLINE static void format_log(char *out, int id, unsigned sum) {
snprintf(out, 256, "req=%d sum=%08x status=ok", id, sum);
}
NOINLINE static void handle(int id, unsigned char *buf, char *line) {
for (int k = 0; k < 20; k++) buf[k] = (unsigned char)(id + k);
format_log(line, id, checksum(buf, 4096));
}
int main(void) {
static unsigned char buf[4096];
char line[256];
for (int id = 0; id < 400000; id++) handle(id, buf, line);
puts(line);
return 0;
}
# 保留帧指针,perf 才能顺着栈往回走(第 5 章讲为什么) $ gcc -O2 -g -fno-omit-frame-pointer hot.c -o hot $ perf stat -e cycles,instructions ./hot $ perf record -F 99 -g ./hot $ perf report --stdio --no-children
在本站的 WSL2 上,程序跑一秒多,perf 采到一百来个样本。报告里大致是这样(只留了前几行):
93.03% hot hot [.] checksum.constprop.0
4.03% hot libc.so.6 [.] 0x000000000017d375
0.95% hot libc.so.6 [.] snprintf
- 校验和占了九成以上,格式化日志的
snprintf在 libc 里,只占几个百分点。要优化,先动checksum。 - libc 那一行只有地址没有函数名:缺 libc 的调试符号。Debian 上装
libc6-dbg后再看。缺符号是剖析里最常见的麻烦(§5.6)。 - 调用链里没有
handle,checksum的上一层直接成了main。反汇编(objdump -d hot)能看到原因:checksum是不调用别的函数的叶子函数,编译器在-O2下没给它建栈帧(没有push %rbp),perf 顺着帧指针往回走时,就从handle的帧直接跳到了main。剖析里看不到某个函数,不等于它没跑;第 13 章讲帧指针回溯和 DWARF 回溯(§13.9.3)。把#define NOINLINE那一行后面的属性删掉(让NOINLINE定义为空)再编译,连checksum也会并进main,报告里只剩main。 perf stat那一行的insn per cycle在本机是 2.7 左右:这个循环几乎不等内存,IPC 很高。把缓冲区改成几十 MB、按跨步访问,再量一次,看 IPC 掉到多少。
- WSL2
- 本站实测过上面四条命令(Debian 13,perf 6.12)。不用 root 就能剖析自己启动的程序;WSL2 这台机器能拿到 cycles 和 instructions 计数。全系统剖析(
-a)和内核栈要 root。 - 服务器
- 对正在跑的服务用
perf record -F 99 -g -p PID -- sleep 30。服务和你是同一个用户时,paranoid 为 2 也能挂上去,只是只看用户态(本站在 WSL2 上试过);服务跑在别的用户下、或者要看内核栈,就要 root 或调低kernel.perf_event_paranoid。云主机上硬件计数器要看实例类型。 - ARM 板
- perf 可以从内核源码的
tools/perf交叉编译,或者在 Buildroot、Yocto 里选上;用帧指针回溯栈时,编译一定加上-fno-omit-frame-pointer,不要依赖编译器的默认值。拿不到 perf 时,最粗的办法是多跑几次cat /proc/PID/task/*/stat看哪个线程的 utime 在涨。
让线程比核多,看饱和度长什么样
起两倍于核数的死循环,CPU 利用率会到 100%,但更值得看的是排队:每个线程有一半时间在等。
# 起 2 × 核数个忙循环,25 秒后自动退出 $ N=$(nproc); for i in $(seq $((N*2))); do timeout 25 sh -c 'while :; do :; done' & done $ vmstat 1 5 $ pidstat -u -C sh 2 1 $ cat /proc/pressure/cpu
本站在 16 核的 WSL2 上看到的(各截了一部分):
vmstat: r b ... us sy id
32 0 ... 100 0 0
pidstat: %usr %system %wait Command
50.96 0.00 48.08 sh
pressure: some avg10=64.87 avg60=32.98
r是 32:16 个在跑,16 个在等。利用率早就 100% 了,它说不出「不够多少」,r和%wait说得出。- 每个 sh 的
%usr和%wait各占一半左右:这就是调度延迟。换成真实的服务,这一半时间就加在每个请求的延迟上。 - PSI 的
avg10一路往上走,avg60跟得慢:和负载平均一样是衰减平均,看趋势要看几个窗口。
- WSL2
- 本站实测过,不需要 root。WSL2 的 PSI 是打开的。
- 服务器
- 别在生产机上跑。要量真实服务的排队时间,用
runqlat-bpfcc 10 1(要 root)。 - ARM 板
- busybox 的
top和/proc/loadavg一般都有;/proc/pressure/要内核开了CONFIG_PSI。没有 pidstat 时,/proc/PID/schedstat的第二个数是这个任务累计在运行队列里等待的纳秒数,隔一秒读两次相减(内核要开CONFIG_SCHED_INFO;本站在 WSL2 上读到过非零值)。
动手:模拟器
先猜:下面这个编出来的服务,真实的 CPU 时间里 compress 占三成多。只采 10 个样本,画出来的火焰图上它会占多少?采到 300 个呢?再点「采样」验证。
火焰图:从采样到图
每点一次「采样」,就在随机时刻打断这个服务一次,记下当时的调用栈。左边是最新的那个栈,右边是到目前为止叠出来的火焰图。点一格放大到它,搜索框里输入函数名看它出现在多少样本里。
最新一个样本(栈顶在上)
火焰图
- 重置后只采 10 次,记下 compress 的占比;再重置、换个时机采 10 次……其实「重置」后结果一样(随机数有固定种子),所以直接再点「采样 100 次」,看占比怎么向 33% 靠拢。样本少时,窄的函数可能根本不出现。
- 点
handle_req那一格放大。它下面的main、event_loop变成灰色的整条:放大后只看这一棵子树,百分比是相对全部样本算的还是相对它算的?看叙述栏。 - 搜索
lock:lock_wait分散在好几条路径上,单看哪一格都不宽,加起来却不小。这就是书里说的「分散但常见」的 CPU 消耗,第三条读图方法。
成书之后
默认调度器从 CFS 换成了 EEVDF
- 书里
- 讲 Linux 的分时调度时以 CFS 为准:按任务已用的 CPU 时间排在红黑树里,优先让用得少的任务上 CPU(§6.4.2)。
- 今天
- Linux 6.6(2023 年)合入了 EEVDF(最早合格虚拟截止时间优先)调度器,取代了 CFS 的选择逻辑;6.12 完成了这次转换。读者的 WSL2 内核是 6.6,已经是 EEVDF。「运行队列」「调度延迟」这些概念和 runqlat 的量法不受影响,但书里关于 CFS 调参的细节(§6.9.3 的 sysctl)在新内核上未必还在,改之前先看自己内核的文档。
调度策略可以用 BPF 程序写了:sched_ext
- 书里
- 调度类是内核里固定的几种(书里列了 RT、O(1)、CFS、Idle、Deadline),用户只能选策略、调优先级(§6.4.2、§6.9.2)。
- 今天
- Linux 6.12 合入了 sched_ext,一个新的调度类,允许把调度策略写成 BPF 程序加载进内核。对排查的影响:遇到调度行为不对劲,先确认机器上有没有加载自定义的调度器。
分节地图
这一章按书的固定套路走:背景(§6.1–6.3)、架构(§6.4)、方法(§6.5)、工具(§6.6)、可视化(§6.7)、实验和调优(§6.8–6.9)。上手只需要概念里讲利用率、饱和度、IPC 的几节,方法里的 USE 和剖析,工具里最常用的几个和火焰图。硬件细节和各个 BPF 工具的说明,用到时再查。
- CPUs drive all software and…约 300 词开篇这一章的学习目标和六个部分,扫一眼当目录用。
- Terminology约 200 词术语处理器、核、硬件线程、逻辑 CPU 怎么区分。
- Models约 100 词模型三个简单模型的引子。
- CPU Architecture约 100 词CPU 架构一颗四核八线程的处理器,在操作系统眼里是八个 CPU。
- CPU Memory Caches约 100 词CPU 缓存越靠近 CPU 越小越快。
- CPU Run Queues约 200 词CPU 运行队列排队的线程数是饱和度的指标;每个 CPU 一个队列的好处。
- Concepts约 100 词概念一组概念的引子,铺垫 IPC。
- Clock Rate约 200 词时钟频率为什么主频高不一定更快。
- Instructions约 100 词指令指令执行的五个步骤,体系结构课内容,跳过不影响后面。
- Instruction Pipeline约 300 词指令流水线流水线和分支预测,同上,用不到。
- Instruction Width约 100 词指令宽度超标量,同上。
- Instruction Size约 100 词指令长度x86 和 ARM 指令长度的差别,排查用不上。
- SMT约 200 词同时多线程超线程的两个逻辑 CPU 不等于两个核。
- IPC, CPI约 300 词IPC 与 CPI高低 IPC 各说明什么,该往哪个方向调。
- Utilization约 200 词利用率利用率怎么算,为什么 CPU 高利用率不像磁盘那样可怕。
- User Time/Kernel Time约 100 词用户态时间与内核态时间两者的比例透露负载类型。
- Saturation约 100 词饱和度满了以后线程排队;云上的限额也会造成饱和。
- Preemption约 100 词抢占高优先级线程插队。
- Priority Inversion约 200 词优先级反转低优先级线程拿着锁挡住高优先级线程;做实时的读者值得看。
- Multiprocess, Multithreading约 300 词多进程与多线程两种用满多核的方式的对比。
- Word Size约 200 词字长32 位和 64 位的差别,今天很少成为问题。
- Compiler Optimization约 100 词编译器优化换个新编译器有时就能变快;细节在第 5 章。
- Architecture约 100 词架构硬件和软件两部分的引子。
- Hardware约 2400 词硬件P/C 状态、各级缓存的延迟、MMU、互连、硬件计数器。要看 PMC 时回来查。
- Software约 1100 词软件调度器的三件事、调度类和策略、空闲线程。改调度策略前读。
- Methodology约 100 词方法十一种方法的总表和作者推荐的顺序。
- Tools Method约 200 词工具法把工具挨个跑一遍;简单,但会漏。
- USE Method约 300 词USE 方法对每个 CPU 查利用率、饱和度、错误;限额环境下怎么算。
- Workload Characterization约 400 词负载特征用几个数描述施加的负载;后面一张值得照着问的清单。
- Profiling约 900 词剖析定时采样和函数跟踪的区别,样本怎么处理,作者读火焰图的三步。
- Cycle Analysis约 300 词周期分析从 IPC 起步往下查停顿类型;要花功夫。
- Performance Monitoring约 100 词性能监控长期监控看哪两个指标,采样间隔为什么不能太粗。
- Static Performance Tuning约 200 词静态性能调优一张检查配置的问题清单:核数、频率、BIOS、微码、限额。
- Priority Tuning约 300 词优先级调优用 nice 给后台任务让路;实时调度类的风险。
- Resource Controls约 100 词资源控制固定上限和份额两种方式。
- CPU Binding约 100 词CPU 绑定绑核和独占 CPU 集合,换缓存热度。
- Micro-Benchmarking约 300 词微基准测试CPU 基准测试常常测的是编译器而不是 CPU。
- Observability Tools约 100 词观测工具工具总表:传统工具和基于 BPF 的工具。
- uptime约 700 词uptime负载平均的来历、在 Linux 上为什么含磁盘,以及 PSI。
- vmstat约 200 词vmstatr、us、sy、id、wa、st 各是什么。
- mpstat约 200 词mpstat按核看各项时间,找热核。
- sar约 100 词sar和 mpstat 对应的选项,能看历史。
- ps约 200 词psps 的 %CPU 是进程一生的平均,不是现在。
- top约 400 词top%CPU 不除以核数;top 自己的开销;短命进程会漏。
- pidstat约 100 词pidstat按进程、线程分开用户态和内核态。
- time, ptime约 200 词time 与 ptime从 real 减去 user 和 sys,估计等待的时间。
- turbostat约 100 词turbostat读处理器 MSR 的工具,只在 x86 上有;云上的虚拟机常常读不到。
- showboost约 100 词showboost作者为 Netflix 环境写的工具,别处未必能用。
- pmcarch约 200 词pmcarch同上,针对 AWS EC2 的 PMC 工具;用 perf stat 代替。
- tlbstat约 200 词tlbstat同上,看 TLB 的 PMC 工具。
- perf约 1600 词perf全系统和单进程剖析、火焰图、调度延迟、硬件事件。要剖析时回来查命令。
- profile约 300 词profileBCC 的剖析器,在内核里汇总,开销更低。
- cpudist约 100 词cpudist线程每次上 CPU 跑多久的分布。
- runqlat约 200 词runqlat量每次排队等了多久;一个被宿主限流的例子。
- runqlen约 200 词runqlen采样队列长度,比 runqlat 便宜。
- softirqs约 100 词softirqs每类软中断花的时间,剖析里常常看不到。
- hardirqs约 100 词hardirqs每个硬中断花的时间。
- bpftrace约 300 词bpftrace现场写单行命令问具体问题;调度器的跟踪点。
- Other Tools约 400 词其他工具lscpu、lstopo、cpupower、valgrind 等;GPU 工具。
- Visualizations约 200 词可视化为什么折线图在几千个 CPU 面前失效。
- Utilization Heat Map约 100 词利用率热图要成规模的机器和专门工具,单机排查用不到。
- Subsecond-Offset Heat Map约 200 词亚秒偏移热图同上;它的用法在 FlameScope 一节会再见到。
- Flame Graphs约 900 词火焰图火焰图的特征、配色、交互和读法。这一章最值得精读的一节。
- FlameScope约 200 词FlameScope在时间上选一小段再画火焰图,找短暂的抖动。
- Experimentation约 100 词实验主动测试时让 mpstat 一直开着。
- Ad Hoc约 100 词临时负载用一个死循环验证工具显示的对不对。
- SysBench约 100 词SysBench一个简单的 CPU 基准测试。
- Tuning约 100 词调优最大的收益来自去掉不必要的工作。
- Compiler Options约 100 词编译选项指向第 5 章。
- Scheduling Priority and Class约 100 词调度优先级和调度类nice、renice、chrt 的用法。
- Scheduler Options约 100 词调度器选项书里自己说很少需要调;新内核换了调度器,表里的参数未必还在。
- Scaling Governors约 200 词频率调节器powersave 和 performance 怎么切;作者劝:锁最高频率没有明显收益,就留在省电模式。
- Power States约 100 词电源状态禁用退出延迟太高的深度睡眠状态。
- CPU Binding约 100 词CPU 绑定taskset 的用法。
- Exclusive CPU Sets约 100 词独占 CPU 集合cpuset 独占和中断亲和性。
- Resource Controls约 100 词资源控制cgroup 的份额和带宽上限,细节在第 11 章。
- Security Boot Options约 100 词安全相关的启动参数关掉漏洞缓解换性能,作者自己不推荐,知道有这回事即可。
- Processor Options (BIOS Tuning)约 100 词处理器选项(BIOS 调优)默认值通常就是最好的;做基准测试时才关睿频。
- Exercises约 300 词习题习题;动手部分本页已经给了替代。
词与短语
只收先读和用到再查两档的节。斜体那一行是原句里连续的几个词,在电子书里搜它就能找到那一句。
§6.3.1 Clock Rate
§6.3.8 Utilization
§6.3.10 Saturation
§6.3.13 Multiprocess, Multithreading
§6.5 Methodology
§6.5.1 Tools Method
§6.5.2 USE Method
§6.5.4 Profiling
§6.5.5 Cycle Analysis
§6.5.6 Performance Monitoring
§6.5.8 Priority Tuning
§6.5.11 Micro-Benchmarking
§6.6.1 uptime
§6.6.2 vmstat
§6.6.3 mpstat
§6.6.6 top
§6.6.13 perf
§6.6.16 runqlat
§6.7 Visualizations
§6.7.3 Flame Graphs
§6.9.4 Scaling Governors
诊断题:换成你来查
场景和输出都是本站编的。先自己判断,再点选项看解释。
场景:一台 8 核服务器跑你的 C++ 网关,用户反馈高峰期变慢。你截了一秒的 mpstat:
CPU %usr %sys %soft %idle all 13.1 1.2 0.3 85.4 0 3.0 1.0 0.0 96.0 3 98.0 2.0 0.0 0.0 5 2.0 1.0 1.0 96.0 (其余各核和 0 号相近)
平均值把一个满载的核摊薄了。3 号核的时间几乎全在 %usr,说明是用户态代码在跑;如果是中断,会体现在 %irq 或 %soft;被偷走会体现在 %steal。下一步用 pidstat -t -p PID 1 或 top -H 找出那个线程,再对它剖析。网关里常见的是单个事件循环线程或者一把大锁让工作串行化。
场景:一块 4 核 ARM 板在写日志到 eMMC,cat /proc/loadavg 显示 6.2,可 busybox 的 top 里 CPU 空闲接近 70%。ps 里有几个线程的状态是 D。
Linux 从 1993 年起把不可中断睡眠的线程算进负载平均,所以负载高不等于 CPU 不够。CPU 空闲七成、又有 D 状态的线程,指向存储:写 eMMC 慢、或者同步写太频繁。下一步看 IO 那边(第 8、9 章),内核开了 PSI 的话 /proc/pressure/io 能直接给出 IO 停顿的比例。负载平均给三个数,不只是 15 分钟的。
场景:8 核虚拟机,服务的 p99 延迟在每天凌晨备份时翻倍。备份期间你看到:
vmstat: r us sy id
18 82 17 1
pidstat: %usr %wait Command
41.0 38.5 server
60.3 12.0 gzip
pressure: some avg10=44.10r 是核数的两倍多,服务有近四成时间在等 CPU,PSI 也高:CPU 饱和了,抢 CPU 的是 gzip。最直接的是降低备份的优先级或限额,书里正是拿监控代理和备份当 nice 的例子(§6.5.8)。加线程只会让排队更长;绑到一个核等于把服务限制在一个核上;换更快的 CPU 也许有一点帮助,但贵,而且没有解决两件事在抢的问题。
场景:你给服务画了一张 CPU 火焰图。左边一座窄塔顶上是 load_config,右边最宽的塔顶上是 encode_frame。同事说:「看,程序先花一点时间加载配置,然后大部分时间在编码。」
火焰图的横轴是样本的集合,按字母顺序排开,位置没有时间含义;要看时间顺序得用火焰图表(flame chart)或者 FlameScope。宽度是样本数,不是调用次数,采样根本不知道调用了几次:宽可能是因为慢,也可能是因为调用多。顶上才是采样那一刻在 CPU 上的函数,底下是祖先。
场景:一个查表很多的统计服务,单核 100%。你用 perf 量了它十秒:
41,200,000,000 cycles 11,500,000,000 instructions # 0.28 insn per cycle
IPC 0.28 属于低的一端:每条指令平均要等三个多周期,典型原因是访存未命中。书里说这种负载换高主频的 CPU,提升达不到预期,因为等内存的时间不会跟着主频缩短(§6.3.7)。下一步用 perf stat -e cache-misses,LLC-load-misses 一类的事件确认,再看数据结构是不是太散、能不能改善局部性。「指令太多」对应的是 IPC 高的情况。
值得停下来的句子
My suggestion is to use the following, in this order: performance monitoring, the USE method, profiling, micro-benchmarking, and static performance tuning.— §6.5。本站译:我的建议是按这个顺序用:性能监控、USE 方法、剖析、微基准测试、静态性能调优。
十一种方法摆在面前,最容易的是挑自己熟的工具先跑。作者给了顺序,而且这个顺序是从便宜到贵:监控数据现成就有;USE 几分钟能扫完;剖析要采样、要符号;微基准测试要搭环境;静态调优要一项项核对配置。本站「方法」一节的七步就是照这个思路排的。
they are only a starting point, so you shouldn’t spend more than five minutes considering them before moving on to other metrics.— §6.6.1。本站译:(负载平均)只是起点,琢磨它们别超过五分钟,然后就该去看别的指标。
这句话说在负载平均那一节的开头,后面作者却花了几页讲它的来历和算法。两者不矛盾:要知道它包含什么(Linux 上包括等磁盘的线程),才知道为什么不能拿它下结论。排查时它只回答「在涨还是在落」,剩下的交给 mpstat、vmstat 和 PSI。
带走的几条
一句话:先分清忙和不够,再按核、按线程找到是谁,最后用采样把时间落到函数上。
8 核平均 12% 可能是一个核满载。先 mpstat -P ALL,再看总数。
§6.6.3利用率到 100% 就不再增长,说不出缺多少。r 长期大于核数、%wait 不为零,才是 CPU 不够。
§6.5.2Linux 上它含等磁盘的线程。三个数比一比,涨还是落,然后去看别的。
§6.6.1top(默认)、ps 里两个线程跑满就是 200%;ps 的还是进程一生的平均。
§6.6.6perf record -F 99 -g,采几十秒。缺符号就装调试符号,栈断了就保留帧指针。
§6.5.4宽度是样本数。先看最宽的塔,再看顶上的平台,再搜分散的小格子。
§6.7.3周期在等内存时,先查缓存和数据布局。
§6.3.7