7. CPU/GPU负载优化:减少CPU唤醒次数、使用GPU加速图像处理、降低DSP负载

各位好,我是老张。这一章我们来聊聊CPU/GPU的负载优化。说实话,相机功耗里最让人头疼的就是CPU和GPU的调度问题。你想想看,相机一开,CPU就得不停干活,GPU也得跟着忙活,DSP也不能闲着——这三个家伙要是没协调好,功耗直接起飞。

我个人习惯把这一章分成三个核心方向:减少CPU唤醒次数用GPU分担CPU压力降低DSP的无效负载。咱们一个一个来拆解。

核心原则:能少干活就少干活,能并行就别串行,能用专用硬件就别用通用硬件。

CPU/GPU负载优化核心策略 负载优化 减少CPU唤醒次数 批量处理中断 / 合并帧回调 使用epoll替代轮询 降低帧率 / 动态帧率调节 GPU加速图像处理 RenderScript / OpenCL Vulkan Compute Shader YUV→RGB转换 / 滤镜 降低DSP负载 避免重复的3A计算 合理配置ISP pipeline 关闭不用的图像处理模块

7.1 减少CPU唤醒次数——别让CPU老醒

CPU唤醒,说白了就是让CPU从睡眠状态切到工作状态。每次唤醒都要消耗不少能量,就像你半夜被吵醒,再想睡着又得折腾半天。相机场景下,CPU被频繁唤醒的罪魁祸首通常是:帧中断传感器回调应用层轮询

我在项目中遇到过这样一个案例:某款手机预览时CPU占用率高达40%,但实际有效计算量只有10%。剩下的30%全浪费在无意义的唤醒和上下文切换上了。后来怎么解决的?我跟你讲讲。

7.1.1 批量处理中断

相机传感器每帧都会产生中断。如果每帧都唤醒CPU,那30fps就是每秒唤醒30次。其实很多中断处理可以合并——比如把3帧的中断放到一起处理。

// 优化前:每帧都唤醒
void sensor_irq_handler() {
    wake_up_cpu();
    process_frame();
}

// 优化后:批量处理
void sensor_irq_handler_batched() {
    if (batch_count++ < BATCH_THRESHOLD) {
        // 只记录,不唤醒
        queue_frame();
        return;
    }
    wake_up_cpu();
    process_batch();
    batch_count = 0;
}

小技巧:BATCH_THRESHOLD一般设2~3比较合适。设太大延迟会变高,用户能感觉到快门延迟。我一般从2开始试,看延迟和功耗的平衡点。

7.1.2 用epoll替代轮询

很多老代码喜欢用while循环轮询某个状态位。这简直是CPU杀手。我曾经在一个第三方算法库中看到这样的代码:

// 错误示范:忙等
while (!is_frame_ready()) {
    // 空转,CPU 100%
}

正确的做法是用epoll或者fence机制。让CPU在事件到来前安心睡觉。

// 正确做法:epoll等待
int epfd = epoll_create(1);
struct epoll_event ev;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

// CPU在这里可以进入深度睡眠
epoll_wait(epfd, &events, 1, -1);

7.1.3 动态帧率调节

不是所有场景都需要30fps。比如你只是打开相机取景,画面静止不动,那降到15fps甚至10fps用户根本感觉不出来。我习惯的做法是:

  • 静止场景:降到10~15fps
  • 轻微运动:20fps
  • 快速运动或扫码:30fps

这个策略在低端机上效果特别明显。我记得有一次在骁龙6系平台上,光这一项就省了80mW的功耗。

7.2 使用GPU加速图像处理

CPU做图像处理,说白了就是拿大刀绣花——不是不能做,但效率太低。GPU天生就是干这个的。尤其是YUV转RGB、色彩校正、滤镜这些操作,GPU比CPU快一个数量级。

7.2.1 RenderScript(老项目首选)

如果你还在维护Android 8~10的项目,RenderScript是个不错的选择。虽然Google已经弃用了,但现有代码还能用。

// RenderScript YUV转RGB示例
#pragma version(1)
#pragma rs java_package_name(com.example.camera)

rs_allocation inputY;
rs_allocation inputUV;

uchar4 __attribute__((kernel)) yuvToRgb(uint32_t x, uint32_t y) {
    uchar y_val = rsGetElementAt_uchar(inputY, x, y);
    uchar uv_val = rsGetElementAt_uchar(inputUV, x / 2, y / 2);
    // 转换逻辑...
    return convertToRgb(y_val, uv_val);
}

注意:RenderScript在Android 12以上已经不建议用了。新项目建议直接用Vulkan Compute Shader或者OpenCL。我去年有个项目就因为用了RenderScript,在Android 14上跑出了奇怪的性能问题。

7.2.2 Vulkan Compute Shader(推荐)

Vulkan是现在的主流。它的Compute Shader可以充分利用GPU的并行计算能力。我个人习惯把图像处理拆成多个小kernel,每个kernel处理一个独立的步骤。

// Vulkan Compute Shader 片段
#version 450
layout(local_size_x = 16, local_size_y = 16) in;
layout(binding = 0) uniform sampler2D inputImage;
layout(binding = 1) uniform writeonly image2D outputImage;

void main() {
    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    vec4 color = texelFetch(inputImage, pos, 0);
    // 做色彩校正
    color.rgb = applyColorMatrix(color.rgb);
    imageStore(outputImage, pos, color);
}

这里有个坑:不要频繁创建和销毁Vulkan管线。管线创建开销很大,我见过有人每帧都重新创建,结果GPU负载比CPU还高。正确的做法是在初始化时创建好,运行时只更新uniform buffer。

7.2.3 什么时候该用GPU?

操作类型 CPU效率 GPU效率 建议
YUV→RGB转换 必须用GPU
3A统计计算 用CPU或DSP
滤镜/美颜 GPU首选
人脸检测 看算法,NPU更好

7.3 降低DSP负载

DSP(数字信号处理器)是相机里专门干图像处理的硬件。它功耗低、效率高,但也不是万能的。DSP最怕的是重复计算无效配置

7.3.1 避免重复的3A计算

3A(自动曝光、自动对焦、自动白平衡)是DSP的主要工作之一。但很多场景下,3A参数变化很小,没必要每帧都重新算。我曾经在调试一个项目时发现,DSP每帧都在算AE,但画面亮度根本没变——白白浪费了算力。

解决方案:设置3A计算的触发阈值。只有画面变化超过一定比例时,才触发重新计算。

// 3A计算触发条件
if (abs(current_luminance - last_luminance) > LUMINANCE_THRESHOLD) {
    trigger_3a_recalculation();
} else {
    // 复用上次的结果
    use_last_3a_result();
}

经验值:LUMINANCE_THRESHOLD我一般设5%~10%。太灵敏了省不了电,太迟钝了画面会忽明忽暗。你可以根据具体传感器调一下。

7.3.2 合理配置ISP pipeline

ISP(图像信号处理器)的pipeline里有很多模块:去噪、锐化、色彩校正、Gamma校正等等。不是所有模块在所有场景下都需要打开。比如在暗光下,锐化模块反而会放大噪点,不如关掉。

我习惯的做法是做一个场景-配置映射表

场景 去噪 锐化 色彩增强 HDR
白天室外
暗光
扫码

这样DSP只做必要的工作,负载自然就降下来了。

7.3.3 关闭不用的图像处理模块

这个听起来很简单,但很多项目里都有这样的问题:初始化时把所有模块都打开了,后面也没关。比如你明明不需要人脸检测,但DSP还在跑人脸检测的算法。我曾经接手过一个项目,DSP负载一直下不来,最后发现是一个调试用的边缘检测模块忘了关——关了之后直接降了30%的DSP负载。

避坑指南:每次切换场景时,记得检查一下哪些模块可以关掉。不要偷懒只开不关。我曾经因为一个没关的HDR模块,导致预览功耗多了150mW,查了两天才找到原因。

7.4 总结一下

CPU/GPU/DSP的优化,说白了就是让合适的硬件干合适的活。CPU擅长控制逻辑,GPU擅长并行计算,DSP擅长固定算法。别让CPU去干GPU的活,也别让DSP去干CPU的活。

我个人习惯在项目初期就做好负载分配方案,而不是等功耗炸了再回头优化。你想想看,如果一开始就把中断合并、GPU加速、DSP配置这些做好,后面能省多少事?

嗯,这一章就到这里。下一章我们会聊聊更底层的硬件调优,到时候见。


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