14. 功耗问题定位:抓取systrace、分析wakeup原因、识别异常高功耗模块
功耗优化做到一定阶段,你会发现一个尴尬的事实:理论算得再漂亮,不如实际抓一把trace来得实在。我见过太多团队,拿着数据手册算功耗算得头头是道,结果一上真机,wakeup次数直接爆表。所以这一章,咱们就来聊聊怎么用systrace这把手术刀,精准解剖相机功耗问题。
14.1 抓取systrace的正确姿势
很多人抓systrace就是简单敲个命令,然后抱怨「怎么啥都看不出来」。其实这里头有门道。我个人习惯分两步走:先抓全局,再抓局部。
14.1.1 全局抓取:看整体轮廓
先跑一个不带过滤的trace,看看系统整体在干嘛。命令很简单:
# 抓取10秒,包含所有进程
python systrace.py -t 10 -o camera_overview.html
但这里有个坑——默认的buffer太小。相机场景下事件密集,buffer一满,后面的数据就丢了。我曾经因为这个浪费了一整天,怎么都复现不了问题,后来才发现是trace被截断了。
-b 8192 参数。
14.1.2 局部抓取:聚焦相机进程
全局trace看个大概后,就该聚焦了。我一般会加上进程过滤:
# 只抓camera相关进程和surfaceflinger
python systrace.py -t 10 -o camera_detail.html \
-b 8192 \
--app=camera \
-a com.android.camera,surfaceflinger
为什么要带上surfaceflinger?因为相机预览的合成、显示都在它身上,很多wakeup其实是从这里冒出来的。
14.2 分析wakeup原因:找到「谁在半夜敲门」
拿到trace后,第一件事就是看wakeup。说白了,wakeup就是CPU从睡梦中被叫醒。每次叫醒都要耗电,叫得越频繁,电耗得越快。
14.2.1 识别wakeup来源
在systrace里,wakeup事件通常表现为一条条细线——从idle状态跳转到running状态。我习惯用这个方法来定位:
- 看频率:打开trace后,先看CPU Frequency那一栏。如果频率频繁跳变,说明wakeup很密集。
- 看进程:点中一个wakeup事件,看是哪个进程触发的。常见的有:
camera provider:HAL层在干活surfaceflinger:合成帧导致mediaserver:编码器在跑kworker:内核驱动在搞事情
- 看binder调用:很多wakeup其实是binder跨进程通信导致的。trace里binder的图形化表示很直观,一眼就能看出谁在频繁调用谁。
我的经验: 相机场景下,最烦人的wakeup来源往往是 camera provider 里的某个传感器轮询。比如某些陀螺仪驱动,每10ms就唤醒一次CPU去读数据。你想想看,相机预览时帧率才30fps,也就是33ms一帧,你10ms就唤醒一次,这不是白费电吗?
14.2.2 分析wakeup的合理性
不是所有wakeup都是坏的。比如vsync信号导致的wakeup,那是正常的。但有些wakeup就值得怀疑了:
| wakeup类型 | 合理场景 | 异常场景 |
|---|---|---|
| vsync | 每帧一次,用于同步渲染 | 频率超过显示刷新率(如120Hz屏却每秒触发200次) |
| 传感器数据 | OIS/EIS需要时,按需读取 | 即使OIS未开启,驱动仍在轮询 |
| 编码器 | 录制时按帧处理 | 预览阶段编码器也在跑(浪费) |
| 网络请求 | 上传/下载照片时 | 后台频繁同步元数据 |
嗯,这里要注意:不要只看wakeup次数,还要看每次wakeup持续了多久。有时候一次长wakeup比十次短wakeup更耗电。因为长wakeup意味着CPU在高频状态停留了很久。
14.3 识别异常高功耗模块
wakeup分析完了,接下来就是找「谁在偷电」。我一般用三个维度来定位:
14.3.1 看CPU负载分布
在systrace里,CPU负载用色块表示。颜色越深,负载越高。我习惯先看这几个地方:
- 大核(prime core):如果大核频繁被唤醒,但干的活很少,那就有问题。比如某个线程只是做个简单的状态检查,却跑在了大核上。
- GPU:相机预览时GPU负载应该很低。如果GPU一直高负载,可能是渲染管线出了问题。
- DSP:有些平台用DSP做图像处理。如果DSP负载异常,可能是算法没优化好。
一个小技巧: 在trace里搜索 sched_wakeup 事件,然后按进程分组统计。这样能快速找出「wakeup大户」。我写过一个小脚本,自动解析trace里的wakeup事件并排序,省了不少事。
14.3.2 看锁竞争
锁竞争是功耗的隐形杀手。为什么?因为一个线程拿着锁不放,其他线程就得等着,等的时候CPU不能睡,只能空转。这在trace里表现为:
- 一个线程在running状态,但实际没干活(被锁阻塞)
- 多个线程在同一个锁上排队
- binder线程池被占满
我曾经遇到过一个案例:相机预览时,某个HAL层的锁设计不合理,导致每次帧处理都要等50ms。这50ms里CPU一直在高频跑,但实际有效工作只有5ms。说白了,45ms的电都白费了。
14.3.3 看帧处理流水线
相机功耗的另一个大头是帧处理流水线。在trace里,你可以看到每一帧从传感器读出、经过ISP、送到应用层、再到显示的全过程。我一般关注这几个点:
- 帧间隔是否均匀:如果帧间隔忽大忽小,说明流水线有瓶颈。
- 是否有重复处理:比如同一帧数据被编码了两次(预览和录制各一次),但明明可以复用。
- buffer是否被过度拷贝:每次拷贝都意味着内存带宽的消耗,也就是电。
14.4 知识体系总览
说了这么多,我画个图帮你理一下思路。整个功耗定位流程,其实就是一个「从宏观到微观」的排查过程:
这个流程我用了很多年,每次都能快速定位到问题。说白了,功耗优化不是靠猜,而是靠trace里的每一根线条、每一个事件来说话。
14.5 实战案例:一次典型的wakeup排查
最后分享一个真实案例。有一次,我发现相机预览时功耗比竞品高了200mW。抓了trace一看,wakeup次数是竞品的3倍。进一步分析发现:
- 有个叫
cam_sensor_worker的线程,每8ms唤醒一次 - 它唤醒后只做了一件事:读一个寄存器,判断传感器是否就绪
- 但传感器其实早就就绪了,这个轮询完全是多余的
解决方案很简单:改成中断触发,而不是轮询。改完后,wakeup次数直接降了70%,功耗也降了150mW。你看,有时候问题就这么简单,但你不抓trace,永远发现不了。
总结一下: systrace是功耗优化的眼睛。学会看wakeup、看负载、看锁竞争,你就能在功耗优化的路上少走很多弯路。记住,每一次wakeup都是有代价的——要么是电,要么是性能。
公众号:蓝海资料掘金营,微信deep3321