Android trace中线程/进程对GPU耗用的定量/定性分析

Android trace中线程/进程对GPU耗用的定量/定性分析

摘要:在Android平台上进行GPU性能分析时,需要注意CPU和GPU工作模型的本质差异。由于GPU采用异步提交、队列执行的架构,传统CPU调度分析方式并不完全适用。主要分析手段包括:

  1. 工具链:
  • Perfetto:用于系统级GPU负载、频率、帧时间线分析
  • Android GPU Inspector(AGI):提供drawcall、shader等细粒度分析
  • 厂商专用工具(如高通Adreno Profiler、ARM Mali工具等)
  1. 关键分析方法:
  • 通过GPU busy状态和频率判断整体负载
  • 追踪RenderThread、SurfaceFlinger等关键线程
  • 分析frame timeline和fence信号时间
  • 结合driver事件(如kgsl/mali事件)进行上下文关联
  1. 精度限制:
  • 原生trace通常无法精确到线程/方法级别
  • 需通过代码注入debug marker实现细粒度分析
  • Vulkan比OpenGL ES更易实现精确追踪
  1. 最佳实践:
  • 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:

  1. FrameTimeline 是否有 missed frame;

  2. App RenderThread 是否正常提交;

  3. SurfaceFlinger 是否等 App buffer;

  4. GPU busy 是否升高;

  5. GPU frequency 是否拉满;

  6. 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

可以尝试:

  1. Perfetto 中看所有活跃进程的 RenderThread / GLThread;

  2. 看 SurfaceFlinger layer 对应关系;

  3. 看 FrameTimeline 中各 layer 的 frame;

  4. 如果有 kgsl/kbase 事件,建立 context 到 pid 的映射;

  5. 用 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

有用的AI大模型网站