CPU 性能分析:用 perf/top/htop 定位热点
做嵌入式 GUI 优化,我最先看的就是 CPU。为什么?因为 GUI 卡顿、掉帧、响应慢,十有八九是 CPU 扛不住了。你想想看,一个 200MHz 的 MCU 要同时处理触摸输入、帧缓冲刷新、图层合成、动画计算,稍微哪个环节吃多了 CPU,画面就给你颜色看。
这一章,咱们就聊聊怎么用 perf、top、htop 这三个工具,把 CPU 热点揪出来。说白了,就是找到「谁在偷懒、谁在拼命干活」。
4.1 先搞清楚:用户态 vs 内核态
在分析 CPU 之前,得先明白两个概念:用户态和内核态。我刚开始做嵌入式时,看到 top 输出里 sys 和 us 两个字段,完全不知道啥意思。后来踩了坑才明白——
- 用户态(us):应用程序代码跑的时间。比如你的 GUI 框架、业务逻辑、算法计算。
- 内核态(sy):系统调用、驱动、中断处理、内存管理这些内核代码跑的时间。
为什么要区分?因为优化方向完全不同。用户态高,你得优化应用代码;内核态高,你得看驱动、中断、系统调用是不是太频繁。
经验法则:嵌入式 GUI 场景下,用户态占比通常 60%-80% 是正常的。如果内核态超过 40%,说明系统调用或驱动层有问题,得优先排查。
4.2 top/htop:快速扫一眼
我个人习惯,先跑 top 或 htop 看一眼全局。这两个工具就像 CPU 的「体检报告」,几秒钟就能告诉你谁在吃 CPU。
4.2.1 top 基本用法
# 嵌入式 Linux 终端
top -d 1 -n 5
输出里重点关注这几列:
| 列名 | 含义 | GUI 场景常见值 |
|---|---|---|
| %CPU | 进程 CPU 占用率 | GUI 进程通常 20%-60% |
| %US | 用户态占比 | 正常 60%+ |
| %SY | 内核态占比 | 超过 30% 要警惕 |
| %NI | 优先级调整后的 CPU | 一般接近 0 |
| %ID | 空闲 CPU | 低于 20% 说明负载高 |
小技巧:按 1 键可以查看每个 CPU 核心的使用情况。多核系统里,如果某个核心跑满 100%,其他核心空闲,说明任务没有均衡分配。
4.2.2 htop 更直观
htop 比 top 好看多了,支持鼠标操作、颜色区分、树形视图。我一般用 htop 做初步排查:
# 安装 htop(如果系统没有)
apt-get install htop
# 运行
htop
htop 的界面里,CPU 条用不同颜色表示:
- 绿色:用户态
- 红色:内核态
- 蓝色:低优先级(nice)
- 黄色/橙色:等待 I/O
一眼看过去,如果红色条特别长,嗯,内核态有问题。
4.3 perf:真正的性能分析利器
top 和 htop 只能看个大概,真要定位热点,还得靠 perf。perf 是 Linux 内核自带的性能分析工具,能采样 CPU 执行路径,告诉你哪行代码最耗时。
4.3.1 安装与基本采样
# 嵌入式 Linux 上安装 perf
apt-get install linux-tools-common linux-tools-$(uname -r)
# 采样 10 秒,记录 CPU 周期事件
perf record -e cycles -a -g -- sleep 10
# 生成报告
perf report
这里 -g 参数很重要,它开启调用链采样。没有调用链,你只知道哪个函数耗 CPU,但不知道是谁调了它。
注意:嵌入式设备上 perf 可能不支持硬件事件(比如 Cortex-A 系列需要 PMU 支持)。如果 perf list 看不到 cycles 事件,可以用 cpu-clock 或 task-clock 替代。
4.3.2 解读 perf report
perf report 的输出长这样:
Samples: 1K of event 'cycles'
Event count (approx.): 1234567890
Overhead Command Shared Object Symbol
45.2% gui_app libgui.so lv_draw_sw_blend
12.8% gui_app libgui.so lv_refr_area
8.5% gui_app [kernel.kallsyms] do_softirq
6.1% gui_app libc.so memcpy
...
看到没?lv_draw_sw_blend 占了 45.2% 的 CPU。这就是热点。我在项目中遇到过类似情况,GUI 卡顿就是因为软件混合(blend)函数太慢,后来换成硬件加速,CPU 占用直接降到 10% 以下。
4.3.3 用户态 vs 内核态分开看
perf 可以分别统计用户态和内核态:
# 只看用户态
perf report --user
# 只看内核态
perf report --kernel
为什么要分开?有一次我排查一个 GUI 刷新慢的问题,用户态看起来很正常,但内核态里 do_softirq 占了 30%。后来发现是触摸中断太频繁,每秒触发上千次,把 CPU 都耗在中断处理上了。
4.4 实战:定位 GUI 的 CPU 热点
咱们拿一个实际场景来走一遍流程。假设你的 GUI 应用叫 gui_app,跑在嵌入式 Linux 上,用户反馈滑动列表时卡顿。
4.4.1 第一步:top 看全局
top -p $(pidof gui_app)
输出:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 root 20 0 45000 12000 8000 R 78.5 12.3 0:45.2 gui_app
CPU 占用 78.5%,其中 us 60%,sy 18%。内核态 18% 不算太高,但用户态 60% 需要深入看。
4.4.2 第二步:perf 采样
perf record -e cycles -p 1234 -g -- sleep 5
perf report
报告显示:
38.2% gui_app libgui.so lv_draw_sw_blend
15.6% gui_app libgui.so lv_refr_area
10.3% gui_app libgui.so lv_anim_exec
8.1% gui_app libc.so memcpy
...
热点很明确:lv_draw_sw_blend 占 38.2%。这是软件混合函数,负责把图层叠加到一起。
4.4.3 第三步:深入调用链
用 perf report -g graph 看调用链:
lv_draw_sw_blend
|- lv_draw_sw_img_decoded
| |- lv_img_decoder_read_line
| |- lv_draw_sw_alpha_blend
|- lv_draw_sw_fill
|- lv_draw_sw_map
原来每次刷新都调用了 lv_img_decoder_read_line 去读图片数据。我一看,图片是 32 位 ARGB 格式,没有预乘 alpha,导致混合时每个像素都要做除法运算。
优化方案:把图片转为预乘 alpha 格式,混合时用移位代替除法。改完后 lv_draw_sw_blend 的 CPU 占用从 38% 降到了 12%。
4.5 内核态热点排查
如果内核态占比高,perf 同样能定位。我遇到过最典型的是中断处理:
perf record -e cycles:k -a -g -- sleep 5
perf report --kernel
输出:
25.3% [kernel] [k] do_softirq
18.7% [kernel] [k] __do_softirq
12.1% [kernel] [k] tasklet_action
8.9% [kernel] [k] irq_exit
看到 do_softirq 占 25%,说明软中断太频繁。进一步查是哪个中断:
cat /proc/interrupts
发现触摸屏中断每秒触发 2000+ 次。原来驱动里没有做去抖(debounce),每次触摸状态变化都触发中断。加上硬件去抖后,中断频率降到 100 次/秒,内核态 CPU 占用从 25% 降到 3%。
避坑指南:我曾经在一个项目里,GUI 刷新时内核态飙到 50%,查了半天发现是帧缓冲驱动每次写像素都调用 copy_to_user,改成 DMA 批量传输后,问题解决。
4.6 本章知识体系
下面这张图总结了 CPU 性能分析的完整流程:
4.7 总结几个要点
- 先全局后局部:top/htop 扫一眼,perf 再深入。
- 区分用户态和内核态:优化方向完全不同。
- 调用链是关键:只看热点函数不够,要知道谁调了它。
- 嵌入式设备注意:perf 可能不支持硬件事件,用 cpu-clock 替代。
最后提醒一句:别在性能分析上花太多时间。我见过有人 perf 调了一整天,结果发现是电源管理策略导致 CPU 降频。先确认基础环境没问题,再动手分析。