4. 性能瓶颈分类

做嵌入式 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 性能分析的完整流程:

CPU 性能分析流程 CPU 性能分析流程 1. 全局观察 2. 热点定位 3. 深入分析 top / htop 查看 us/sy 占比 识别高 CPU 进程 perf record 采样 perf report 看热点 区分 us/kernel 热点 perf report -g 调用链 定位具体函数/行号 制定优化方案 用户态热点高? 内核态热点高? 两者都高? 优化应用代码 减少算法复杂度 优化驱动/中断 减少系统调用 检查内存/缓存 考虑硬件加速 工具:top → htop → perf record → perf report → 优化

4.7 总结几个要点

  • 先全局后局部:top/htop 扫一眼,perf 再深入。
  • 区分用户态和内核态:优化方向完全不同。
  • 调用链是关键:只看热点函数不够,要知道谁调了它。
  • 嵌入式设备注意:perf 可能不支持硬件事件,用 cpu-clock 替代。

最后提醒一句:别在性能分析上花太多时间。我见过有人 perf 调了一整天,结果发现是电源管理策略导致 CPU 降频。先确认基础环境没问题,再动手分析。