Android trace中线程/进程对GPU耗用的定量/定性分析
摘要:在Android平台上进行GPU性能分析时,需要注意CPU和GPU工作模型的本质差异。由于GPU采用异步提交、队列执行的架构,传统CPU调度分析方式并不完全适用。主要分析手段包括:
- 工具链:
- Perfetto:用于系统级GPU负载、频率、帧时间线分析
- Android GPU Inspector(AGI):提供drawcall、shader等细粒度分析
- 厂商专用工具(如高通Adreno Profiler、ARM Mali工具等)
- 关键分析方法:
- 通过GPU busy状态和频率判断整体负载
- 追踪RenderThread、SurfaceFlinger等关键线程
- 分析frame timeline和fence信号时间
- 结合driver事件(如kgsl/mali事件)进行上下文关联
- 精度限制:
- 原生trace通常无法精确到线程/方法级别
- 需通过代码注入debug marker实现细粒度分析
- Vulkan比OpenGL ES更易实现精确追踪
- 最佳实践:
- Perfetto用于系统时间线分析
- AGI用于帧级GPU工作分解
- 业务代码中添加CPUtrace和GPU debug marker
这种分层分析方法可以准确定位GPU性能瓶颈,从系统级到方法级实现全面诊断。
Android trace 里想像 CPU scheduling slice 那样精确拆 GPU 占用,通常做不到完全等价。
GPU 是异步提交、队列执行、驱动调度的,线程只是"提交 GPU 命令",真正 GPU 执行时已经脱离了原始 Java/C++ 方法调用栈。
但可以通过 Perfetto / AGI / 厂商 GPU trace / ftrace 事件 / Vulkan 或 OpenGL 标记 做到不同层级的定性或半定量分析。
1. 先明确:GPU 占用和 CPU 占用不是同一种模型
CPU trace 里可以看到:
-
哪个线程在 CPU 上跑;
-
跑了多久;
-
被谁抢占;
-
调用栈是什么;
-
可以比较准确算出某个线程在一段时间内消耗了多少 CPU 时间。
但 GPU 不一样:
App Thread / RenderThread
|
| 提交 OpenGL / Vulkan / HWUI 命令
v
GPU Driver
|
| 命令缓冲区 / command buffer / job
v
GPU Queue
|
| 异步执行
v
GPU Hardware
也就是说:
-
CPU 线程只是提交工作;
-
GPU 真正执行时,不一定知道对应哪个 Java 方法;
-
多个进程的 GPU job 可能交错;
-
SurfaceFlinger 也可能参与 GPU composition;
-
有些 GPU job 是系统服务、RenderEngine、HWC、driver 内部产生的;
-
很多设备的 GPU driver 不开放足够细粒度的 per-process / per-thread GPU execution 信息。
所以想要拆成几类:
| 目标 | 可行性 |
|---|---|
| 看整体 GPU busy / freq / load | 比较可行 |
| 看某段时间 GPU 是否忙 | 可行 |
| 看 App 是否导致 GPU 忙 | 可行,偏定性 |
| 看哪个进程提交了 GPU 工作 | 部分设备可行,依赖 driver trace |
| 看哪个线程提交了 GPU 工作 | 可通过 CPU trace / userspace trace 反推 |
| 看哪个方法导致 GPU 工作 | 需要 App 自己加 marker / trace label |
| 像 CPU slice 一样精确算每个线程 GPU 占用 | 通常不可行 |
2. 工具链
2.1 Perfetto
这是 Android 平台上最通用的 trace 工具。
可以看:
-
CPU scheduling;
-
RenderThread;
-
Choreographer;
-
HWUI;
-
SurfaceFlinger;
-
FrameTimeline;
-
GPU frequency;
-
GPU memory;
-
fences;
-
部分设备上的 GPU counter / GPU slice;
-
kgsl / mali / kbase 等厂商 driver ftrace 事件,如果开放。
适合做:
-
系统级 GPU 问题定位;
-
掉帧分析;
-
App 渲染链路分析;
-
SurfaceFlinger / HWUI / GPU 关系分析。
2.2 Android GPU Inspector,AGI
AGI 更适合做 GPU 细节分析,尤其是:
-
Vulkan command buffer;
-
OpenGL ES 调用;
-
GPU counter;
-
render pass;
-
draw call;
-
texture / buffer;
-
shader;
-
pipeline;
-
GPU queue;
-
frame capture。
适合回答:
-
某一帧 GPU 时间花在哪里;
-
哪个 render pass 慢;
-
哪些 draw call 重;
-
shader 是否复杂;
-
overdraw 是否严重;
-
bandwidth 是否高。
如果要做到"方法级"或"渲染 pass 级",AGI 往往比 Perfetto 更合适。
2.3 厂商工具
不同 GPU 厂商有自己的工具:
| GPU | 工具 |
|---|---|
| Qualcomm Adreno | Snapdragon Profiler / Adreno Profiler / AGI |
| ARM Mali | Streamline / Mali Graphics Debugger / AGI |
| Imagination PowerVR | PVRTune / PVRCarbon |
| Samsung Xclipse / AMD RDNA | 厂商工具,视设备支持情况 |
这些工具通常能看到更丰富的 GPU counter,例如:
-
GPU utilization;
-
fragment busy;
-
vertex busy;
-
tiler busy;
-
shader core busy;
-
texture unit busy;
-
bandwidth;
-
cache miss;
-
overdraw;
-
memory transaction。
3. 在 Perfetto 里能看哪些 GPU 相关信息?
3.1 整体 GPU busy / frequency
常见轨道:
-
GPU Frequency;
-
GPU Utilization;
-
GPU Busy;
-
GPU Memory;
-
power / thermal 相关 counter。
但注意:
很多设备只提供全局 GPU busy,不提供 per-process GPU busy。
也就是说你能看到:
14:00:00 - 14:00:01 GPU busy 95%
14:00:01 - 14:00:02 GPU busy 70%
但未必能直接看到:
com.xxx.app 占 60%
SurfaceFlinger 占 25%
system_server 占 5%
3.2 App 渲染链路
Perfetto 里重点看这些线程:
app main thread
app RenderThread
app GLThread,如果有
app Vulkan render thread,如果有
SurfaceFlinger
RenderEngine
hwc service
Binder threads
典型 App 渲染路径:
Main Thread
Choreographer#doFrame
View traversal
measure / layout / draw
|
v
RenderThread
DrawFrame
syncFrameState
record display list
flush drawing commands
|
v
GPU Driver
|
v
GPU
|
v
fence signaled
|
v
SurfaceFlinger
latch buffer
compose
present
在 Perfetto 中可以看:
-
App 是否在
doFrame超时; -
RenderThread 是否有长时间
DrawFrame; -
是否卡在
dequeueBuffer/queueBuffer; -
是否等待 GPU fence;
-
SurfaceFlinger 是否合成慢;
-
present 是否延迟;
-
GPU frequency 是否拉高;
-
GPU busy 是否在对应时间段升高。
3.3 FrameTimeline
Android 12 以后,FrameTimeline 很重要。
可以看到:
-
Expected Timeline;
-
Actual Timeline;
-
App frame;
-
SurfaceFlinger frame;
-
missed frame;
-
jank reason;
-
present time;
-
deadline。
如果 GPU 慢,经常会看到类似:
-
App deadline missed;
-
SurfaceFlinger deadline missed;
-
GPU completion late;
-
Buffer ready late;
-
Present delayed。
不过 FrameTimeline 不一定告诉"哪个 shader 慢",它更像是告诉"这一帧在哪个阶段超时"。
3.4 fence / sync timeline
GPU 是异步的,所以 fence 非常重要。
需要看:
-
App queueBuffer 后,buffer 什么时候 ready;
-
SurfaceFlinger 等待 buffer 的时间;
-
acquire fence;
-
release fence;
-
present fence;
-
GPU completion fence。
如果看到 SurfaceFlinger 或 App 等 fence 很久,通常说明:
CPU 已经提交了命令,但 GPU 还没做完。
这类问题通常是 GPU bound 或 composition bound。
4. 如何尝试抓取 GPU 相关 trace?
4.1 先看设备支持哪些 GPU ftrace 事件
可以执行:
adb shell "cat /sys/kernel/tracing/available_events | grep -Ei 'gpu|kgsl|mali|kbase|dma_fence|sync'"
或老路径:
adb shell "cat /sys/kernel/debug/tracing/available_events | grep -Ei 'gpu|kgsl|mali|kbase|dma_fence|sync'"
如果是 Qualcomm Adreno,可能会看到:
kgsl/...
例如:
kgsl/kgsl_pwrlevel
kgsl/kgsl_gpubusy
kgsl/kgsl_gpu_frequency
kgsl/kgsl_context_create
kgsl/kgsl_context_destroy
kgsl/kgsl_drawobj
kgsl/kgsl_submit
具体名字因 kernel 和 vendor 实现不同。
如果是 ARM Mali,可能会看到:
mali/...
kbase/...
例如:
mali/mali_job_slots_event
mali/mali_pm_status
mali/mali_gpu_power_state
kbase/...
但是很多量产 user build 会裁剪掉这些事件。
4.2 Perfetto 抓取配置示例
一个偏系统渲染分析的配置可以包括:
buffers {
size_kb: 131072
fill_policy: RING_BUFFER
}
duration_ms: 10000
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
# 下面这些需要设备支持,名字不一定完全一致
ftrace_events: "dma_fence/dma_fence_init"
ftrace_events: "dma_fence/dma_fence_signaled"
# Qualcomm 设备可能支持
ftrace_events: "kgsl/kgsl_pwrlevel"
ftrace_events: "kgsl/kgsl_gpubusy"
ftrace_events: "kgsl/kgsl_gpu_frequency"
atrace_categories: "gfx"
atrace_categories: "view"
atrace_categories: "wm"
atrace_categories: "am"
atrace_categories: "input"
atrace_categories: "freq"
atrace_categories: "idle"
atrace_categories: "sched"
atrace_categories: "binder_driver"
atrace_apps: "*"
}
}
}
data_sources {
config {
name: "android.surfaceflinger.frametimeline"
}
}
data_sources {
config {
name: "android.gpu.memory"
}
}
data_sources {
config {
name: "linux.process_stats"
target_buffer: 0
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 1000
}
}
}
使用方式:
adb push gpu_trace_config.pbtx /data/local/tmp/gpu_trace_config.pbtx
adb shell perfetto \
-c /data/local/tmp/gpu_trace_config.pbtx \
-o /data/misc/perfetto-traces/gpu_trace.pftrace
adb pull /data/misc/perfetto-traces/gpu_trace.pftrace .
然后打开:
https://ui.perfetto.dev/
5. 如何分析"这一段时间 GPU 被谁用掉了"?
可以按照这个路径分析。
第一步:确定这段时间 GPU 是否真的忙
看:
-
GPU busy;
-
GPU frequency;
-
GPU power level;
-
thermal;
-
frame miss;
-
fence wait。
如果现象是:
GPU busy 高
GPU freq 高
App / SurfaceFlinger 等 fence
CPU 没有明显长任务
基本可以判断是 GPU bound。
第二步:看同一时间段哪些进程在提交渲染工作
重点看:
目标 App
SurfaceFlinger
system_server
launcher
WebView / Chrome
相机 / 视频进程
其他悬浮窗 / overlay / live wallpaper
在 Perfetto 中找:
-
App 的 RenderThread;
-
是否频繁
DrawFrame; -
是否有长时间 OpenGL / Vulkan submit;
-
SurfaceFlinger 是否有长时间 composition;
-
是否存在其他 App 同时刷新画面。
典型情况:
com.demo.app RenderThread 正在连续 DrawFrame
GPU busy 同步升高
SurfaceFlinger 每帧等待该 App buffer
这时可以定性判断:
GPU 主要由 com.demo.app 的渲染触发。
但这仍然不是"精确 GPU slice 归因"。
第三步:看 SurfaceFlinger 是否使用 GPU composition
即使 App 渲染不重,SurfaceFlinger 也可能因为合成策略导致 GPU 忙。
常见触发原因:
-
多层透明合成;
-
blur;
-
rounded corner;
-
shadow;
-
dim layer;
-
screen recording;
-
virtual display;
-
rotation;
-
color transform;
-
HDR / SDR 混合;
-
不支持 HWC overlay;
-
protected content;
-
特殊格式 buffer;
-
多窗口 / 悬浮窗。
可以看 SurfaceFlinger 相关 slice:
SurfaceFlinger
onMessageInvalidate
onMessageRefresh
commit
composite
RenderEngine
如果 SurfaceFlinger / RenderEngine 在 GPU busy 高的时候也很活跃,那么 GPU 占用可能来自合成,而不是 App 自身绘制。
第四步:看 fence 等待是谁造成的
如果:
SurfaceFlinger 等待某个 App buffer 的 acquire fence
通常说明 App 提交的 GPU work 没完成。
如果:
App 等待 release fence / dequeueBuffer 卡住
可能说明:
-
SurfaceFlinger 没及时释放 buffer;
-
GPU backlog;
-
buffer queue 堵塞;
-
三缓冲被占满;
-
前面帧 GPU 太慢。
第五步:结合 driver ftrace 做 per-context 分析
如果设备支持 kgsl / mali / kbase trace,那就有机会进一步拆。
Qualcomm Adreno
可能可以看到:
-
context create;
-
context id;
-
draw object submit;
-
GPU busy;
-
frequency;
-
power level;
-
fence;
-
pid / process name,部分设备有。
理论上可以建立映射:
pid / process -> kgsl context id -> GPU submit -> GPU completion
然后近似算:
某进程 GPU active time = 该进程 context 的 job duration 之和
但是限制很大:
-
不同 kernel 事件字段不同;
-
有些只有 context id,没有 pid;
-
有些只有 submit,没有 completion;
-
有些 duration 是 queue duration,不是真正 hardware active duration;
-
多 engine / overlap 情况下不能简单相加;
-
user build 可能没有这些事件。
ARM Mali
Mali 可能通过 kbase/mali 事件看到:
-
job slot;
-
queue;
-
context;
-
GPU power state;
-
job start / end;
-
tiler / fragment / compute activity。
但同样高度依赖内核配置和厂商开放程度。
6. 能不能精确到线程?
一般来说:
GPU 执行阶段通常不能精确归因到 Android 线程。
原因是:
-
线程提交 command buffer 后就返回了;
-
GPU job 在 driver queue 里异步执行;
-
同一个进程多个线程可能提交到同一个 GL context / Vulkan queue;
-
driver 内部可能重排、合批、延迟提交;
-
GPU 执行时只知道 context / queue,不一定知道原始线程;
-
OpenGL ES 尤其难,因为 driver 内部隐式行为很多。
可以做到的是:
6.1 CPU 提交线程归因
例如:
com.demo.app RenderThread
DrawFrame
eglSwapBuffers
glDraw*
vkQueueSubmit
这个可以通过 Perfetto / atrace / simpleperf / 自定义 trace 得到。
它表示:
哪个线程在提交 GPU 工作。
但不等于:
GPU 真正被哪个线程占用。
6.2 Vulkan queue 级归因
Vulkan 比 OpenGL 更容易分析,因为 Vulkan 的提交模型更显式。
可以用:
-
vkQueueSubmit; -
command buffer;
-
render pass;
-
debug marker;
-
timestamp query;
-
AGI capture;
-
Perfetto Vulkan layer,视环境支持情况。
如果在代码里加:
vkCmdBeginDebugUtilsLabelEXT(...)
vkCmdEndDebugUtilsLabelEXT(...)
或者:
vkCmdWriteTimestamp(...)
就可以更接近地知道:
哪个 pass / 哪段 GPU command 花了多久。
6.3 OpenGL ES debug marker
OpenGL ES 可以用:
glPushDebugGroupKHR(...)
glPopDebugGroupKHR(...)
或:
glInsertEventMarkerEXT(...)
不过 OpenGL 的异步和 driver 优化更黑盒,精度通常不如 Vulkan。
7. 能不能精确到方法?
默认不能。
如果你想知道:
method A 造成 GPU 10ms
method B 造成 GPU 5ms
一般必须自己做标记。
Java / Kotlin 层
可以加:
Trace.beginSection("DrawAvatarList")
...
Trace.endSection()
这会出现在 Perfetto 里,但它是 CPU 侧标记。
它可以知道:
哪个业务逻辑触发了哪些渲染提交。
但不能直接说明 GPU 执行耗时。
Native / Vulkan 层
可以加 GPU marker:
VkDebugUtilsLabelEXT label = {};
label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
label.pLabelName = "RenderShadowPass";
vkCmdBeginDebugUtilsLabelEXT(cmd, &label);
// draw calls
vkCmdEndDebugUtilsLabelEXT(cmd);
再配合 AGI 或厂商 profiler,就可以看到 GPU 侧:
RenderShadowPass: 3.4ms
RenderMainPass: 6.2ms
PostProcess: 2.1ms
这个才更接近"方法级 GPU 占用"。
8. 常见trace分析SOP
场景 A:App 掉帧,怀疑 GPU 慢
看 Perfetto:
-
FrameTimeline 是否有 missed frame;
-
App RenderThread 是否正常提交;
-
SurfaceFlinger 是否等 App buffer;
-
GPU busy 是否升高;
-
GPU frequency 是否拉满;
-
fence 是否晚 signal。
如果是:
CPU 侧 doFrame 不长
RenderThread 很快进入 eglSwapBuffers
SurfaceFlinger 等 acquire fence
GPU busy 90%+
结论:
大概率 GPU bound。
下一步用 AGI 看:
-
draw call;
-
render pass;
-
shader;
-
texture bandwidth;
-
overdraw;
-
framebuffer resolution;
-
MSAA;
-
blur / shadow / postprocess。
场景 B:想知道是 App 还是 SurfaceFlinger 导致 GPU 忙
看:
App RenderThread activity
SurfaceFlinger composition activity
HWC composition strategy
RenderEngine activity
如果:
App 每帧正常渲染,GPU busy 高;
SurfaceFlinger 主要是 latch/present,没有明显 GPU composition;
更像 App 自己渲染重。
如果:
App 本身帧不重;
SurfaceFlinger / RenderEngine 有明显 GPU composition;
存在多层透明、模糊、录屏、虚拟屏;
更像 SF 合成造成 GPU 压力。
场景 C:多进程同时渲染,想拆 per-process
可以尝试:
-
Perfetto 中看所有活跃进程的 RenderThread / GLThread;
-
看 SurfaceFlinger layer 对应关系;
-
看 FrameTimeline 中各 layer 的 frame;
-
如果有 kgsl/kbase 事件,建立 context 到 pid 的映射;
-
用 GPU job start/end 近似估算每个 context 的 GPU 时间。
但要注意:
没有 driver 级事件时,只能定性推断,不能准确量化。
9. 可用的近似量化方法
如果设备提供 GPU job slices,可以用类似方式算:
process_gpu_time = sum(gpu_job_duration for jobs owned by process)
gpu_share = process_gpu_time / total_gpu_busy_time
但如果只有全局 GPU busy,可以做粗略相关性分析:
某时间段内:
- GPU busy 高;
- com.demo.app RenderThread 连续提交;
- SurfaceFlinger 等 com.demo.app 的 fence;
- 其他进程无明显渲染;
可以定性为:
com.demo.app 是主要 GPU 压力来源。
如果要量化,可以粗略写成:
该 2s 时间窗内 GPU busy 平均 85%。
主要活跃 GPU 提交方为 com.demo.app RenderThread。
未观察到其他高频渲染进程。
因此 com.demo.app 是主要 GPU 占用来源,但 trace 不支持精确 per-process 归因。
这类结论在性能报告里比较严谨。
10. Perfetto SQL 分析思路
如果 trace 里有 GPU slice,可以在 Perfetto UI 的 Query 页面尝试查表。
不同版本 schema 不完全一样,可以先看有哪些 GPU 相关表:
SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name LIKE '%gpu%';
可能看到:
gpu_slice
gpu_counter_track
如果有 gpu_slice,可以尝试:
SELECT *
FROM gpu_slice
LIMIT 20;
如果里面有 context、queue、duration 等字段,就可以继续分析。
如果 GPU slice 能关联到 track:
SELECT
s.ts,
s.dur,
s.name,
gt.name AS track_name
FROM gpu_slice s
JOIN gpu_track gt ON s.track_id = gt.id
ORDER BY s.ts
LIMIT 100;
实际字段以你的 trace 为准。
如果只有 GPU counter,可以查:
SELECT
c.ts,
c.value,
t.name
FROM counter c
JOIN gpu_counter_track t ON c.track_id = t.id
LIMIT 100;
或者先查所有 counter 名称:
SELECT DISTINCT name
FROM counter_track
WHERE name LIKE '%GPU%'
OR name LIKE '%gpu%';
11. 真正想要"像 CPU slice 一样"的话,最佳实践是这样
方案一:Perfetto 做系统时间线
用 Perfetto 观察:
-
App thread;
-
RenderThread;
-
SurfaceFlinger;
-
FrameTimeline;
-
fences;
-
GPU busy;
-
GPU freq;
-
driver events。
得到结论:
哪一段时间 GPU 忙;
与哪些进程/线程提交渲染相关;
是否是 App、SurfaceFlinger、HWC、录屏、其他 overlay 引起。
方案二:AGI 做 GPU 细粒度归因
用 AGI capture 一帧或多帧,分析:
-
render pass;
-
draw call;
-
pipeline;
-
shader;
-
texture;
-
bandwidth;
-
overdraw;
-
GPU duration。
得到结论:
这一帧 GPU 时间具体花在哪些 pass / draw call 上。
方案三:业务代码加 marker
在 Java/Kotlin 加:
Trace.beginSection("HomeFeed.draw")
...
Trace.endSection()
在 Vulkan/OpenGL 加:
GPU debug marker
timestamp query
得到:
CPU 业务阶段 <-> GPU command <-> GPU duration
这是最接近"方法级 GPU 拆解"的做法。
12. 总结
简单说:
Android trace 里通常不能天然做到像 CPU slice 一样,精确统计每个线程、每个方法的 GPU 占用。
但是可以通过 Perfetto 看到全局 GPU busy、frame、fence、SurfaceFlinger、RenderThread 和部分 driver GPU job,再结合 AGI 或厂商工具做 GPU pass/draw call 级分析。
如果想做到方法级归因,需要应用侧主动加 CPU trace marker 和 GPU debug marker。
建议实践路径:
1. Perfetto 抓系统 trace
2. 先判断是否 GPU bound
3. 看 App RenderThread / SurfaceFlinger / fence
4. 检查是否有 kgsl / mali / kbase driver events
5. 如果需要更细,使用 AGI
6. 如果要方法级,代码里加 Trace section + GPU marker