3. 性能分析工具入门
做 OpenGL ES 性能优化,第一步不是改代码,而是建立“可观测性”。如果不知道一帧时间花在哪里、GPU 在等什么、带宽被谁吃掉,任何优化都只是猜测。本节把常用工具按三个层次梳理:GPU 厂商工具看硬件内部、帧调试器看单帧绘制调用、系统级工具看 CPU/GPU/内存的整体协同。三者配合,才能形成完整的性能证据链。
一、三层工具的分工与信号链
三类工具并非互相替代,而是从粗到细、从外到内的关系。系统级工具给出全局时间线,帧调试器给出单帧调用序列,GPU 厂商工具给出硬件计数器的微观解释。下图展示了它们在同一帧上的观测层次与数据流向。
二、GPU 厂商工具:读硬件计数器
不同 GPU 架构的计数器名称不同,但关注维度高度一致:着色器周期、纹理采样、顶点处理、片元输出、带宽、Overdraw、Tile 利用率。以 Adreno 为例,Adreno GPU Profiler 可给出每个渲染阶段的耗时占比;Mali 的 Graphics Debugger 则强调 Tile 与 Shader Core 的负载分布。
使用厂商工具时,建议先看三个“体检指标”:
- GPU 利用率:长期低于 60% 往往说明 CPU 提交或同步在拖后腿。
- 带宽占用:纹理读取与帧缓冲写入是否接近峰值,是移动端最常见的瓶颈。
- Overdraw:同一像素被重复着色的次数,UI 与半透明层是重灾区。
三、帧调试器:定位到具体 Draw Call
帧调试器(RenderDoc、Snapdragon Profiler、Xcode GPU Frame Capture)的核心价值是“把一帧拆开”。它能列出每一次 draw call 的着色器、纹理、混合状态、渲染目标,并支持逐像素回放。典型排查路径如下:
- 抓取一帧,按 GPU 时间排序 draw call,找出耗时最高的前 5 个。
- 检查这些 draw call 的着色器复杂度、纹理格式与采样次数。
- 查看渲染目标切换频率,判断是否存在频繁的 FBO 切换与 Tile 回写。
- 用像素历史功能确认某个像素被哪些 draw call 重复写入。
// 典型问题:每帧多次切换 FBO 导致 Tile 回写
glBindFramebuffer(GL_FRAMEBUFFER, fboA); // 离屏渲染
drawScene();
glBindFramebuffer(GL_FRAMEBUFFER, fboB); // 后处理
drawPostProcess();
glBindFramebuffer(GL_FRAMEBUFFER, 0); // 回主屏
drawUI();
// 优化方向:合并 Pass、减少 FBO 切换、使用 loadOp=clear 避免无效回读
四、系统级工具:判断 CPU 还是 GPU 瓶颈
Systrace/Perfetto 与 Android GPU Inspector 提供的是“时间线视角”。关键看三件事:主线程是否被阻塞、RenderThread 与 GPU 是否空转、是否存在垂直同步等待。如果主线程长时间处于 doFrame 之外,说明逻辑或 IO 在拖慢帧率;如果 GPU 时间线出现大段空白,说明 CPU 提交不足。
| 工具层次 | 代表工具 | 核心指标 | 适用场景 |
|---|---|---|---|
| 系统级 | Systrace / Perfetto / AGI | 帧时间、线程状态、VSync | 判断 CPU/GPU 瓶颈归属 |
| 帧调试器 | RenderDoc / Snapdragon Profiler | Draw Call、状态、纹理、像素历史 | 定位单帧异常调用 |
| GPU 厂商 | Adreno Profiler / Mali GDB / PVRTune | Shader 周期、带宽、Overdraw | 硬件级下钻与验证 |
五、指标解读与常见误区
拿到数据不等于找到答案。以下误区在工程中非常常见:
- 只看平均帧率:平均 60 FPS 可能由大量 16ms 与少量 40ms 帧混合而成,卡顿感来自长尾帧。
- 把 GPU 利用率高当成坏事:利用率高说明 GPU 被喂饱,真正的问题是“是否在做无用功”。
- 忽略带宽与功耗:移动端 GPU 往往先撞带宽墙,再撞算力墙。
- 在 Debug 构建下采集:未优化着色器与断言会严重扭曲数据,必须用 Release/Profile 构建。
建议建立一份“性能基线表”:记录设备型号、GPU 型号、分辨率、场景、平均帧时间、GPU 时间、带宽、Overdraw。每次优化后重新采集,用数据而非感觉判断收益。工具只是手段,可复现的测量流程才是性能工程的地基。