CPU占用指标解读:user / sys / iowait 等指标含义
做前台服务优化,你得先看懂 CPU 指标。不然你连瓶颈在哪都不知道。我个人习惯先盯住三个核心指标:user、sys、iowait。它们分别代表什么?跟 Compose 重组抖动又有什么关系?我们一个一个说清楚。
user CPU:你的业务代码在跑
user CPU 指的是用户态占用的 CPU 时间。说白了,就是你写的业务代码在干活。计算、循环、JSON 解析、数据转换……全算在 user 里。
我在项目中遇到过,某个前台服务的 user CPU 突然飙到 80%。一查发现是后台任务在疯狂解析超大 JSON。你想想看,这种纯计算操作不涉及系统调用,也不等 IO,就是实打实地占着 CPU 不放。
user CPU 高会直接挤压 UI 线程的时间片。虽然 Compose 的重组在子线程也能跑,但渲染管线的主线程如果拿不到 CPU,掉帧就来了。
WorkManager 把定期全量排序改成了增量更新,user CPU 直接从 60% 降到了 15%。
sys CPU:系统调用的代价
每次你调用系统 API——文件读写、网络请求、Binder 通信、甚至 Log.d()——都会陷入内核态。这部分开销就是 sys CPU。
我有个经验:如果 sys CPU 长时间超过 30%,说明你的程序在频繁做系统调用,得警惕了。举个例子,用 OkHttp 做网络请求,每次请求都涉及 socket 读写和 binder IPC。请求频率一上去,sys CPU 就跟着涨。
在 Android 前台服务里,Binder 调用是 sys CPU 的主要来源。Service 和 Activity 通信、跨进程传数据,这些都会产生 sys CPU 开销。sys CPU 高对 Compose 重组的影响很直接——UI 线程自己也做系统调用(比如 vsync 同步),如果 sys 被其他线程占满了,UI 线程就得排队等。
怎么查?用 adb shell dumpsys cpuinfo 看整体,再用 top -H -p <pid> 看线程级 sys 占比。我曾经用 Perfetto 抓过一次 trace,发现一个后台线程每秒调用 200 次 System.currentTimeMillis(),累计 sys 开销高得离谱。
iowait:IO 阻塞的代价
iowait 是 CPU 等待 IO 完成的耗时。这个指标很有意思——CPU 其实很闲,但在等磁盘或网络把数据送过来。所以 CPU 利用率不一定高,但性能可能很差。
我记得有一次排查前台服务卡顿,CPU 总利用率只有 30%,但 iowait 占了 40%。一查发现是日志框架在同步写文件,每次写一行日志都要等磁盘 IO。你想想看,UI 线程偶尔也要写日志(比如上报关键事件),这一等就是几十毫秒,掉帧就这么来的。
iowait 高对 Compose 重组的影响很隐蔽。它不是 CPU 不够用,而是线程被 IO 阻塞了。重组任务排在线程池里,前面的 IO 操作不完成,后面的重组就得等着。
BufferedWriter 异步写日志,但忘记设置缓冲区大小,结果每次 flush 还是同步 IO。后来改成 kotlinx.coroutines 配合 Mutex 异步批量写入,iowait 降到了 5% 以下。
irq、softirq 与 idle
这几个指标平时不太起眼,但出了问题很头疼。
- irq / softirq:硬件中断和软中断。一般占比很低,如果异常飙高,可能是驱动问题或网卡负载过重。我见过一台测试机因为 Wi-Fi 驱动 bug,softirq 占了 20% CPU,前台服务每 3 秒卡一次。
- idle:空闲时间。idle 高说明 CPU 在摸鱼。但别高兴太早——如果 idle 高但服务依然卡顿,那大概率是锁竞争或线程调度出了问题。比如一个线程持有锁在等 IO,其他线程只能干等,CPU 看着很闲,但业务就是跑不动。
对于前台服务来说,irq/softirq 异常需要从硬件或驱动层面排查;idle 异常则需要用 Thread Dump 或 SimplePerf 看线程状态。
指标速查表
| 指标 | 含义 | 对前台服务的影响 | 优化方向 |
|---|---|---|---|
| user | 用户态 CPU 占用 | 业务逻辑重,抢占 UI 时间片 | 减少计算,异步化,缓存 |
| sys | 内核态 CPU 占用 | 系统调用频繁,UI 响应变慢 | 减少 Binder 调用,批量操作 |
| iowait | IO 等待耗时 | IO 阻塞导致线程卡顿 | 异步 IO,缓冲区,无阻塞设计 |
| irq/softirq | 中断处理 | 异常时服务周期性卡顿 | 排查驱动,更新固件 |
| idle | 空闲时间 | 高但卡顿 → 锁或调度问题 | 检查锁竞争,优化线程模型 |
实战:怎么看这些指标
工具用对,事半功倍。我个人习惯先看整体,再查细节。
# 整体 CPU 负载(按进程汇总)
adb shell dumpsys cpuinfo
# 实时看指定进程的 CPU 占用
adb shell top -n 1 | grep 你的包名
# 看每个线程的 CPU 占用(前台服务场景)
adb shell top -H -p $(adb shell pidof com.yourapp)
# 用 Perfetto 抓 trace(最推荐)
adb shell perfetto -o /data/misc/perfetto-traces/trace -t 10s \
--config '{"duration_ms":10000,"buffers":[{"size_kb":1024}],"data_sources":[{"config":{"name":"linux.sys_stats"}}]}'
拿到数据后,重点看 user + sys 的总和是否超过 50%,以及 iowait 是否长期 > 10%。如果 user 高,优先优化业务代码;sys 高,砍系统调用;iowait 高,改异步 IO。
我曾经踩过一个坑:用 dumpsys cpuinfo 看到 user 和 sys 都不高,以为没问题。结果用 Perfetto 一看,iowait 在特定场景下冲到 35%,刚好跟 UI 掉帧的时机完全吻合。所以,光看整体指标不够,还得结合时序 trace。
嗯,CPU 指标就这么回事。读懂它们,你就能精准定位前台服务的性能瓶颈。下一步就是针对性地优化——但那是后面章节的事了。