CPU性能瓶颈定位

做系统性能优化,CPU永远是第一个需要盯住的指标。我见过太多同学一上来就搞内存、搞IO,结果折腾半天发现是CPU调度出了问题——说白了,方向都没对。今天咱们就把CPU这层彻底讲透。

CPU使用率分析:别只看百分比

很多人一听到CPU瓶颈,第一反应就是看top。嗯,这没错,但光看%CPU是不够的。我记得有一次线上服务CPU打到80%,我上去一看user态占70%,sys态才不到10%,当时直觉告诉我不是内核问题。结果用perf top一看,是某个业务线程在死循环里做字符串拼接。你说这怪谁?

常用的几个命令,我列一下:

  • top / htop —— 全局视图
  • vmstat 1 —— 看上下文切换、中断、用户/系统时间
  • /proc/stat —— 手动解析,适合脚本监控
  • pidstat -u 1 —— 按线程看利用率

这里有个坑:CPU使用率低不代表没瓶颈。比如大量线程在等锁,user态可能很低,但调度开销巨大。我曾经被一个"CPU才30%,但响应时间超时"的问题搞了三天……后来发现是futex竞争导致调度延迟爆炸。

我的习惯:先跑top看总的idle和sys/user比例,然后立刻切到pidstat -t看哪个线程在消耗CPU。如果sys过高(>30%),多半是系统调用或中断问题,往下看。

调度延迟:被忽视的隐形杀手

你做Android系统优化,调度延迟绕不过去。调度延迟说白了就是:线程已经准备好了,但CPU还在干别的事,没轮到它。这个时间一长,用户就感觉到“卡”。

怎么测?我用得最多的是两个方法:

  1. perf sched —— 记录调度事件,然后生成延迟分布图。
  2. Tracepoint sched_switch —— 配合atrace或者systrace,能看到每个线程被踢出去、被唤醒的时间点。

举个例子,你用perf sched record跑一段时间:

perf sched record -- sleep 5
perf sched latency

输出的表里会看到最大延迟、平均延迟、等待次数。我通常重点看“wait”列,如果某个线程平均等待超过10ms,那这个线程的响应性就已经有问题了。

我曾经踩过的坑:有一次我优化一个GPU渲染线程,发现它总是被一个后台GC线程抢占。看调度延迟表才发现,GC线程的优先级居然被设成了比渲染线程还高。你说这是不是低级错误?但工具不会骗你,一看数据就明白了。

irq/softirq:硬件和内核的“刺客”

irq(硬件中断)和softirq(软中断)是CPU被动的开销。你在top里看到si和hi两项,分别就是软中断和硬中断。

irq过多的典型场景:网卡中断、IO中断、定时器中断。我记得有一次在车载系统上,GPS模块每秒产生几千次中断,CPU被irq占掉了40%,导致UI线程调度延迟飙到几十毫秒。后来我通过/proc/interrupts找到罪魁祸首,然后用irqbalance把中断分散到不同核心,才压下去。

检查命令:

cat /proc/interrupts | head -20  # 看每个CPU上的中断次数
cat /proc/softirqs                # 软中断分布

表格对比一下这两种中断:

类型来源典型占比排查工具
硬中断(irq)硬件设备(网卡、磁盘、定时器)高负载下可达20-30%/proc/interrupts
软中断(softirq)内核下半部(网络收包、时钟、RCU)通常<5%,异常时飙升/proc/softirqs, /proc/stat

如果发现某个CPU上irq特别多,可以用mpstat -I 1实时观察,或者用irqbalance重新分配。但在Android上,有些平台限制了irqbalance,那就得手动设置CPU affinity。

perf事件采样:终极定位武器

前面的方法都是敲门砖,真要到细节层面,还得靠perf的事件采样。perf的威力在于:它能告诉你CPU到底在"跑什么代码",而不是"跑了多少"。这个区别很关键。

我最常用的perf套路:

  1. 用perf top实时看热点函数
  2. 用perf record -g加调用栈采样
  3. 用perf report分析,找出最热路径
perf record -F 99 -g -- sleep 10
perf report --stdio

这里-F 99代表每秒采样99次(避开100Hz的别名问题),-g记录调用栈。输出里你会看到每个函数占CPU的百分比,以及它被谁调用的。

常用的事件有:cycles、instructions、cache-misses、branch-misses。我曾在优化AOSP的SurfaceFlinger时,发现cache-misses异常高,一查是某个高频调用的函数里有个大结构体没有对齐——改成cacheline对齐后,性能直接提升了15%。

一点建议:perf在Android上需要内核开启CONFIG_PERF_EVENTS。如果目标设备是userdebug版,通常没问题。但生产环境可能没有,那时你可以用simpleperf代替——它是Google为Android定制的,用法很相似。

CPU性能瓶颈定位分析流程 开始观察CPU使用率 top / vmstat / pidstat 查看利用率 是否出现CPU异常? 调度延迟分析 (perf sched) 中断检查 (/proc/interrupts) perf事件采样 (record/report) 定位CPU瓶颈 提示:实际分析中这三个分支往往需要结合使用。例如调度延迟高可能由irq过多引起,而perf采样可以确认具体函数。 注意:如果CPU使用率正常但响应慢,先跳过使用率分析,直接检查调度延迟和中断。

你看,整个流程走下来,思路就很清晰了:先从宏观的使用率入手,发现异常后再根据特征走调度延迟、中断、perf采样三条路。这三条路不是互斥的——我经常同时开着vmstat和perf record,一边看全局一边抓细节。

最后说一句,工具只是拐杖,关键是你对CPU工作原理的理解。为什么中断会打断线程?为什么调度延迟会抖动?搞懂这些,遇到问题自然能快速判断方向。嗯,这一章先到这,把这几招练熟,下一层就该看内存了。