4、Profiling工具链:端侧Profiling工具介绍
做端侧AI ISP优化,最怕什么?
怕的是你改了一堆代码,跑起来一看——帧率没变,功耗还涨了。更怕的是,你根本不知道瓶颈在哪。
我刚开始做ISP调试那会儿,就吃过这个亏。对着代码调了三天,结果发现是DDR带宽被某个DMA占死了。嗯,从那以后,我养成了一个习惯:动手优化之前,先上Profiling工具。
这一章,我就把端侧最常用的几把“尺子”给你捋一遍。它们分别是:perf、simpleperf、systrace、atrace,以及我们自己写的自定义计时器。
核心观点:没有Profile就没有优化。工具不是用来炫技的,是用来回答“时间花在哪了”这个问题的。
4.1 perf:内核级的“手术刀”
perf 是Linux内核自带的性能剖析工具。它直接读取PMU(性能监控单元),精度高,开销低。
我一般在定位CPU热点函数或者cache miss的时候用它。比如,你的ISP算法里有个循环特别慢,perf能告诉你——是计算瓶颈,还是内存访问瓶颈。
我的习惯:先用 perf top 看一眼全局热点,再用 perf record 抓具体进程。别一上来就全量采样,数据太多反而看不清。
常用命令示例:
# 查看全局热点(实时)
perf top
# 对指定进程采样10秒
perf record -p <PID> -g -- sleep 10
# 生成报告
perf report -g graph
-g 参数打开调用链,这个很重要。只看函数名不看谁调了它,等于只看到症状没看到病因。
注意:Android 标准内核可能裁剪了 perf 的某些功能。如果遇到 perf_event_open 失败,检查内核配置 CONFIG_PERF_EVENTS 是否开启。
4.2 simpleperf:Android上的“亲儿子”
simpleperf 是Google为Android打造的perf替代品。它解决了两个痛点:一是Android内核碎片化,perf行为不一致;二是它支持Java + C++ 混合调用链。
我记得有一次,一个AI ISP的预处理线程CPU占用异常高。用perf只能看到native层的memcpy热点,但不知道是谁调的。换成simpleperf,调用链直接追溯到Java层的Bitmap处理逻辑——原来是格式转换没走硬件加速。
基本用法:
# 记录采样数据
simpleperf record -p <PID> -g --duration 10 -o perf.data
# 生成报告
simpleperf report -i perf.data --full-call-graph
| 特性 | perf | simpleperf |
|---|---|---|
| 平台 | 通用Linux | Android专用 |
| 调用链支持 | Native only | Java + Native |
| 符号解析 | 依赖debug符号 | 自动解析(需带symbol的so) |
| 开销 | 低 | 低 |
避坑指南:我曾经在64位进程上用了32位的simpleperf,结果符号全解不出来。记住:simpleperf的位数必须跟目标进程一致。
4.3 systrace / atrace:系统级的“录像机”
perf和simpleperf擅长看“哪里慢”,但看不透“为什么慢”。比如,你的ISP pipeline帧率突然掉到15fps,perf告诉你CPU空闲——那问题在哪?
答案往往是:在等。等GPU渲染、等DMA传输、等VSync信号。这时候就需要systrace出场了。
systrace 基于 atrace 内核机制,可以同时抓取CPU调度、进程状态、内核事件、硬件计数器。它把时间轴画出来,你一眼就能看到哪个时间段是“空的”。
抓取命令:
# 抓取10秒,包含sched、gfx、input等标签
python systrace.py -t 10 sched gfx input view dalvik
在Android 10之后,Google主推 Perfetto 替代systrace。但底层机制一样,都是atrace。我个人建议新项目直接上Perfetto,功能更强,UI也更现代。
怎么看systrace?我一般先看CPU Frequence行,看频率有没有被限制;再看SurfaceFlinger,看合成有没有掉帧;最后看你的ISP进程状态——是Running还是Uninterruptible Sleep。如果是后者,大概率在等内核驱动。
4.4 自定义计时器:最后的“底牌”
工具再强,也有覆盖不到的地方。比如,你想精确测量某个ISP算法模块从输入到输出的延迟,或者你想在硬件IP内部打点——这时候,就得自己动手了。
我常用的自定义计时器方案有三种:
- clock_gettime + CLOCK_MONOTONIC:精度ns级,适合用户态函数计时。
- RDTSSC指令:x86平台读取CPU周期计数器,精度最高,但要注意乱序执行和频率变化。
- 硬件timer寄存器:在驱动或固件里直接读硬件cycle counter,适合测量硬件IP处理时间。
一个简单的C++计时器封装:
#include <chrono>
class ScopedTimer {
public:
ScopedTimer(const char* name) : name_(name) {
start_ = std::chrono::steady_clock::now();
}
~ScopedTimer() {
auto end = std::chrono::steady_clock::now();
auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start_).count();
ALOGD("[Timer] %s: %ld us", name_, us);
}
private:
const char* name_;
std::chrono::time_point<std::chrono::steady_clock> start_;
};
// 用法:在函数入口定义即可
void ISPProcessFrame() {
ScopedTimer t("ISPProcessFrame");
// ... 你的处理逻辑
}
注意:自定义计时器会引入测量开销。对于微秒级的函数,多次测量取平均值,去掉首尾异常值。我曾经在1ms的ISP pipeline里插了20个计时器,结果计时器本身占了5%的开销——这就本末倒置了。
4.5 工具选型:什么时候用哪个?
你可能会问:这么多工具,我到底该用哪个?
我的经验是:先粗后细,先系统后局部。
- 第一步:用 systrace/Perfetto 看全局,确认瓶颈在CPU、GPU、DDR还是IO。
- 第二步:如果瓶颈在CPU,用 simpleperf 定位热点函数。
- 第三步:如果热点在某个模块内部,用 自定义计时器 精确测量子函数耗时。
- 第四步:如果怀疑内核或硬件问题,用 perf 抓内核事件和PMU计数器。
一句话总结:systrace看调度,perf看CPU,simpleperf看调用链,自定义计时器看细节。四把尺子配合使用,没有定位不了的瓶颈。