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被截断了。

避坑指南: 抓相机trace时,建议把buffer调到至少8MB。命令加个 -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状态。我习惯用这个方法来定位:

  1. 看频率:打开trace后,先看CPU Frequency那一栏。如果频率频繁跳变,说明wakeup很密集。
  2. 看进程:点中一个wakeup事件,看是哪个进程触发的。常见的有:
    • camera provider:HAL层在干活
    • surfaceflinger:合成帧导致
    • mediaserver:编码器在跑
    • kworker:内核驱动在搞事情
  3. 看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、送到应用层、再到显示的全过程。我一般关注这几个点:

  1. 帧间隔是否均匀:如果帧间隔忽大忽小,说明流水线有瓶颈。
  2. 是否有重复处理:比如同一帧数据被编码了两次(预览和录制各一次),但明明可以复用。
  3. buffer是否被过度拷贝:每次拷贝都意味着内存带宽的消耗,也就是电。

14.4 知识体系总览

说了这么多,我画个图帮你理一下思路。整个功耗定位流程,其实就是一个「从宏观到微观」的排查过程:

相机功耗定位流程 第一步:抓取systrace 全局抓取(看整体轮廓) → 局部抓取(聚焦相机进程) 第二步:分析wakeup原因 识别wakeup来源(谁在唤醒CPU) 判断wakeup合理性(vsync正常 vs 传感器轮询异常) 第三步:识别异常高功耗模块 CPU负载分布(大核是否被滥用) 锁竞争分析(线程空转耗电) 帧处理流水线(重复处理、过度拷贝) 输出:功耗问题根因 + 优化建议

这个流程我用了很多年,每次都能快速定位到问题。说白了,功耗优化不是靠猜,而是靠trace里的每一根线条、每一个事件来说话

14.5 实战案例:一次典型的wakeup排查

最后分享一个真实案例。有一次,我发现相机预览时功耗比竞品高了200mW。抓了trace一看,wakeup次数是竞品的3倍。进一步分析发现:

  • 有个叫 cam_sensor_worker 的线程,每8ms唤醒一次
  • 它唤醒后只做了一件事:读一个寄存器,判断传感器是否就绪
  • 但传感器其实早就就绪了,这个轮询完全是多余的

解决方案很简单:改成中断触发,而不是轮询。改完后,wakeup次数直接降了70%,功耗也降了150mW。你看,有时候问题就这么简单,但你不抓trace,永远发现不了。

总结一下: systrace是功耗优化的眼睛。学会看wakeup、看负载、看锁竞争,你就能在功耗优化的路上少走很多弯路。记住,每一次wakeup都是有代价的——要么是电,要么是性能。


公众号:蓝海资料掘金营,微信deep3321