Optimize Metal apps and games with GPU counters
Graphics & Games 高级 25m

使用 GPU Counters 优化 Metal 应用和游戏

Optimize Metal apps and games with GPU counters

2020年6月25日

在 Apple 官方观看视频

一句话判断

如果你的 Metal 应用性能不达标,但你看不出瓶颈在哪里,GPU Counters 就是你的性能分析显微镜——它告诉你 GPU 的每一个时钟周期都花在了哪里。

这场 Session 讲了什么

这场 Session 深入讲解了如何使用 GPU Counters 来分析和优化 Metal 应用的性能。GPU Counters 是 GPU 硬件级别的性能计数器,它们可以精确地告诉你 GPU 在每个 render pass 和 compute pass 中的资源利用情况。

演讲者首先介绍了 GPU Counters 的分类体系。最关键的几组 Counter 包括:Vertex Processing(顶点处理耗时)、Fragment Processing(片段处理耗时)、Memory Bandwidth(内存带宽消耗)、和 Shader Core Utilization(着色器核心利用率)。每个 Counter 都有对应的”极限值”——Apple GPU 在特定频率下的理论峰值性能。你的目标就是让实际利用率尽可能接近理论峰值。

Session 的中间部分通过三个真实案例演示了如何用 GPU Counters 定位性能瓶颈:一个受限于带宽的后处理效果、一个受限于顶点处理的粒子系统、以及一个受限于片段处理的复杂着色器。每个案例都展示了从”性能差”到”性能好”的优化路径。

最后,演讲者介绍了如何在 Xcode 12 中可视化 GPU Counters,以及如何在代码中使用 MTLCaptureScope 和 MTLCounterSampleBuffer 进行自动化性能采集。

值得深挖的点

  • Counter 的归一化解读:单独看一个 Counter 的绝对值没有意义——你需要将它与峰值性能比较。比如,如果你的 Fragment Processing Counter 显示 40% 利用率,而 Memory Bandwidth 是 95%,那么瓶颈很明显不在着色器计算上,而在内存访问上。优化着色器逻辑不会有帮助,你需要优化数据布局和访问模式。
  • Tile-Based 架构的特殊 Counter:Apple GPU 的 TBDR 架构有一些独特的 Counter,如 Tile Load/Store 的带宽和 HSR(Hidden Surface Removal)的效率。这些 Counter 帮助你理解渲染管线中”隐藏”的开销。

代码片段

// 配置 GPU Counter 采样
let counterSampleBufferDescriptor = MTLCounterSampleBufferDescriptor()
counterSampleBufferDescriptor.counterSet = counterSet  // 选择 Counter 集
counterSampleBufferDescriptor.storageMode = .shared
counterSampleBufferDescriptor.sampleCount = 100

let counterSampleBuffer = try device.makeCounterSampleBuffer(
    descriptor: counterSampleBufferDescriptor
)

// 在 Render Command Encoder 中插入 Counter 采样点
let renderEncoder = commandBuffer.makeRenderCommandEncoder(
    descriptor: renderPassDescriptor
)
// ... 渲染命令 ...

// 在特定点采样 Counter
renderEncoder.sampleCounters(
    in: counterSampleBuffer,
    at: sampleIndex
)
renderEncoder.endEncoding()
// 读取 Counter 采样结果
func resolveCounterSamples(
    _ buffer: MTLCounterSampleBuffer,
    range: Range<Int>
) {
    let data = buffer.resolveCounterRange(range)

    // 解析 Counter 数据
    data.withUnsafeBytes { rawBuffer in
        let samples = rawBuffer.bindMemory(to: MTLCounterResultTimestamp.self)
        for sample in samples {
            let startTime = sample.timestamp
            // 计算耗时等指标
        }
    }
}
// 使用 Instruments 进行 GPU 分析
// 命令行启动 GPU 性能分析:
// xcrun xctrace record \
//   --template 'GPU Performance' \
//   --launch -- /path/to/app

// 或在 Xcode 中:
// Product > Profile > GPU Performance
// 查看 Counter 的实时曲线和火焰图

最佳实践

  • 先用 Xcode 的 GPU Profiler 获取全局 Counter 概览,再针对高消耗的 pass 进行深度分析
  • 关注 Counter 的利用率百分比而非绝对值——50% 以下的利用率意味着有很大优化空间
  • 带宽受限的场景优先考虑纹理压缩(ASTC)和 memoryless render target
  • 顶点处理受限的场景考虑使用 mesh shader 或简化几何体
  • 片段处理受限的场景检查是否存在过度绘制,利用 HSR 优化

还有什么值得关注

  • Xcode 12 的 GPU Profiler 界面有显著改进,Counter 的可视化更直观了
  • 搭配 “Gain insights into your Metal app with Xcode 12” 一起看,可以掌握完整的 Metal 调试工具链
  • GPU Counters 的数据也可以导出为.trace 文件,供离线分析或 CI 集成
WWDC 2020