构建性能瓶颈分析:用 perf、top、iotop 揪出拖慢 Yocto 的真凶
Yocto 构建一次动辄几十分钟甚至几小时,卡在哪一步都不知道,确实让人头疼。我刚开始调优时也犯过傻——盲目加 CPU 核数,结果磁盘 I/O 先爆了,构建反而更慢。说白了,你得先找到瓶颈在哪,才能对症下药。
这一章我们就来聊聊怎么用 perf、top、iotop 这些工具,把 CPU、内存、磁盘 I/O 的瓶颈一个个揪出来。
构建过程的资源消耗画像
Yocto 构建不是单一任务,它分好几个阶段,每个阶段的资源消耗特点完全不同。我习惯把整个过程拆成三块来看:
| 阶段 | 主要消耗 | 典型特征 |
|---|---|---|
| 解析与配置(bitbake -p) | CPU(单核) | Python 解析 .bb/.bbclass,单进程跑满一个核 |
| 编译(do_compile) | CPU(多核)+ 内存 | gcc/clang 并行编译,内存吃紧时触发 OOM |
| 打包与部署(do_package / do_image) | 磁盘 I/O | 大量读写 tmp/deploy,rpm/deb 打包、压缩 |
你想想看,如果编译阶段 CPU 利用率只有 30%,那加核也没用;如果打包阶段 iowait 飙到 50%,那换 SSD 比换 CPU 更管用。所以第一步,先搞清楚你现在卡在哪。
核心原则:先测量,再调优。不要凭感觉加资源,用数据说话。
用 top 快速定位 CPU 和内存压力
top 是我最常用的第一手工具。构建时开一个终端跑 top,按 1 看每个核的负载,按 Shift+M 按内存排序。我个人习惯这样用:
# 在构建主机上运行
top -d 2 -b | grep -E "bitbake|gcc|make|python" > /tmp/build_profile.log
重点关注几个指标:
- %CPU:如果所有核加起来不到 80%,说明 CPU 不是瓶颈,可能是 I/O 或锁等待。
- %MEM / RES:单个进程 RES 超过物理内存 20% 就要小心了,尤其是 gcc 进程。
- load average:如果 load 远高于 CPU 核数,说明大量进程在等 I/O 或锁。
我记得有一次帮客户调一个嵌入式 Linux 发行版,构建时 load 飙到 60,但 CPU 利用率只有 40%。用 top 一看,大量进程处于 D 状态( uninterruptible sleep),说白了就是在等磁盘。换了一块 NVMe SSD,构建时间直接砍半。
小技巧:按 Shift+P 按 CPU 排序,按 Shift+M 按内存排序。两个视图切换着看,能快速定位是 CPU 吃紧还是内存吃紧。
用 iotop 揪出磁盘 I/O 瓶颈
top 能告诉你 iowait 高,但谁在读写磁盘?这时候 iotop 就派上用场了。iotop 类似 top,但显示的是每个进程的磁盘读写速率。
# 安装 iotop(Yocto 构建主机上)
sudo apt install iotop # 或 sudo dnf install iotop
# 运行 iotop,只看 Yocto 相关进程
sudo iotop -o -d 2 -p $(pgrep -d',' -f "bitbake|gcc|make|python")
参数说明:
-o:只显示有磁盘 I/O 的进程-d 2:每 2 秒刷新一次-p:指定进程 PID,避免被其他进程干扰
看什么?重点关注 DISK READ 和 DISK WRITE 两列。如果某个进程持续写几百 MB/s,而你的磁盘带宽只有 500 MB/s,那它基本就是瓶颈了。
嗯,这里要注意:iotop 需要 root 权限,否则看不到进程名。我一般用 sudo iotop,然后按 O 键过滤,只显示 Yocto 相关进程。
避坑指南:我曾经在 HDD 上跑 Yocto,do_package 阶段 iotop 显示 DISK WRITE 持续 200+ MB/s,但 HDD 顺序写只有 150 MB/s。结果构建时间从 40 分钟拖到 2 小时。后来换成 SSD,问题迎刃而解。如果你还在用 HDD 做构建,强烈建议先换 SSD。
用 perf 做深度 CPU 剖析
top 和 iotop 能告诉你「谁」在消耗资源,但 perf 能告诉你「为什么」。perf 是 Linux 性能剖析的瑞士军刀,可以采样 CPU 的调用栈,找出热点函数。
# 对 bitbake 进程做 CPU 采样,持续 30 秒
sudo perf record -g -p $(pgrep -f bitbake) -- sleep 30
# 生成报告
sudo perf report -g graph --no-children
输出示例:
# 样本数:15000
# 事件:cpu-clock
#
# 开销 命令 共享对象 符号
# ........ ........... ...................... ......................
35.2% bitbake [kernel.kallsyms] [k] _raw_spin_lock
12.1% bitbake libpython3.10.so.1.0 [.] PyEval_EvalFrameDefault
8.5% bitbake libc-2.31.so [.] __GI___libc_read
6.3% bitbake [kernel.kallsyms] [k] do_syscall_64
看到 _raw_spin_lock 占 35%?说明有严重的锁竞争。这在 Yocto 解析阶段很常见——bitbake 解析数千个 .bb 文件时,全局解释器锁(GIL)和文件锁会拖慢速度。
我建议重点关注几个方向:
- 内核锁竞争:
_raw_spin_lock、mutex_lock占比高 → 考虑减少并行任务数(BB_NUMBER_THREADS) - Python 解释器:
PyEval_EvalFrameDefault占比高 → 考虑用 pypy3 或优化 .bb 解析逻辑 - 系统调用:
do_syscall_64、__GI___libc_read占比高 → 检查文件系统或缓存策略
个人经验:perf 的 -g 参数会记录调用栈,但数据量很大。我一般先跑 10 秒采样,看看热点在哪,再针对性地加长时间。另外,--no-children 可以去掉子调用聚合,只看当前函数的开销,更清晰。
用 vmstat 和 iostat 看系统整体健康度
除了进程级工具,系统级工具也不能少。vmstat 和 iostat 能给你一个全局视角。
# 每 2 秒输出一次系统状态
vmstat 2
# 输出示例
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
8 2 0 2048000 500000 8000000 0 0 500 1200 3000 5000 45 10 5 40 0
关键列:
- r:运行队列长度。如果持续大于 CPU 核数,说明 CPU 不够用。
- b:处于不可中断睡眠的进程数。如果持续大于 0,说明 I/O 有瓶颈。
- wa:iowait 百分比。超过 20% 就要警惕了。
- si / so:swap 换入换出。如果非零,说明内存不足,正在用磁盘当内存,性能会急剧下降。
iostat 更聚焦磁盘:
# 查看所有磁盘的 I/O 统计,每 2 秒一次
iostat -x 2
# 输出示例
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 200.0 150.0 80000.0 60000.0 2.5 0.8 28.0
sda 50.0 300.0 20000.0 120000.0 15.0 6.0 90.0
看 %util:如果接近 100%,说明磁盘已经饱和。再看 await:如果远高于 svctm,说明有排队,磁盘响应慢。
实战判断口诀:
- r 高 + wa 高 + %util 高 → I/O 瓶颈,换 SSD 或调 BB_NUMBER_THREADS
- r 高 + wa 低 + %util 低 → CPU 瓶颈,加核或优化编译参数
- si/so 非零 → 内存瓶颈,加内存或减少并行任务
瓶颈分析全景图
下面这张图总结了整个分析流程,我每次调优都按这个思路走:
实战案例:一次完整的瓶颈定位
最后分享一个我实际遇到的案例。某次构建一个带 Qt 的嵌入式镜像,总耗时 2 小时 15 分,客户嫌慢。我按上面的流程走了一遍:
- vmstat:r=12(8 核 CPU),wa=35%,si/so=0 → 排除内存问题,I/O 嫌疑最大
- iotop:do_package 阶段,DISK WRITE 持续 180 MB/s,磁盘是 SATA SSD(理论 550 MB/s)→ 磁盘没跑满,但 wa 高说明有排队
- perf:采样发现
do_syscall_64和__GI___libc_write占 40% → 大量小文件写入导致系统调用开销大 - 根因:do_package 阶段会为每个包创建大量小文件(.rpm/.deb 的临时文件),文件系统 ext4 的元数据操作成了瓶颈
- 解决方案:把
TMPDIR移到 tmpfs(内存盘),同时调大BB_NUMBER_THREADS到 12(超线程)
结果:构建时间从 2 小时 15 分降到 1 小时 10 分,接近一半。你想想看,只是换了个文件系统策略,效果就这么明显。
注意:tmpfs 方案需要足够的内存。如果内存小于 32GB,不建议把整个 TMPDIR 放 tmpfs,否则会触发 swap,得不偿失。我一般只把 tmp/deploy 或 tmp/work 放 tmpfs,具体看哪个阶段是瓶颈。
好了,这一章的内容就到这里。工具和方法都给了,剩下的就是动手去试。记住:先测量,再调优,别凭感觉加资源。