Find and fix performance issues in your Metal games
Xcode & Developer Tools 高级 21m

查找并修复 Metal 游戏中的性能问题

Find and fix performance issues in your Metal games

2026年6月10日

在 Apple 官方观看视频

一句话判断

✅ 告别“无法复现的掉帧”,系统级无感回溯 (Lookback Collection) 结合 StateReporting API,让长线游戏性能排查从“玄学”变成“精准定位”。

这场 Session 讲了什么

这场 Session 最值得关注的一件事,就是 Apple 终于把“性能回溯”做进了系统底层。以前我们抓游戏掉帧,要么靠玩家反馈后艰难复现,要么挂着 Instruments 跑几个小时把设备跑发烫。现在 macOS 和 iOS 会在后台默默记录 Metal 性能指标并保存数天,掉帧了直接“时光倒流”抓取过去 5 小时的 trace。

配合全新的 metalperftrace 命令行工具和 StateReporting API,你不仅能拿到系统底层的无感数据,还能把自定义的游戏状态(比如“火山关卡+高画质”)直接印在时间轴上。这对重度 Metal 游戏(特别是 3D 大作、长时间运行的多人游戏)的性能优化工程师来说,绝对是改变工作流的杀手级特性。

值得深挖的点

系统级 Lookback Collection:用功耗换“后悔药”

Apple 做这个的动机很纯粹:长线游玩的性能衰减(如发热降频、内存泄漏)在短时间的 desk testing 中根本暴露不出来。但后台一直记录性能数据,必然面临功耗和存储的 trade-off。Apple 的解法是“聚合指标 + 可选逐帧”以及“高效存储”,数据只保留几天。

和旧方案相比,这简直是降维打击。以前我们要自己写一套 OSLog 打点系统,或者用第三方 SDK 疯狂上报。现在系统底层兜底,macOS 直接敲一行 metalperftrace collect --last 5h 就能拿到过去 5 小时的 .atrc 文件。坑在于 iOS 端目前还需要玩家(或测试员)在控制中心手动点击“Performance Trace”按钮来导出,这显然是 Apple 为了兼顾隐私和后台功耗做的妥协,自动化测试流水线暂时还吃不到这个红利。

StateReporting API:给冷冰冰的数据加上“游戏上下文”

拿到掉帧数据后,最痛苦的是不知道当时玩家在干嘛。是在看风景,还是在放全屏大招?StateReporting API 就是为了解决这个“上下文缺失”的问题。它设计了 Domain(域)、State(状态)和 Metadata(元数据)三层结构。

这个设计非常巧妙,它区分了 stable metadata(稳定元数据,如关卡 ID)和 volatile metadata(易变元数据,如当前血量)。在 Instruments 和 CLI 中,状态转换会直接变成时间轴上的标记。和竞品(如 Unreal 自带的 Insights)相比,StateReporting 是系统级的,能跨引擎使用,且和 MetricKit 深度绑定。容易踩的坑是 Domain 设计得太细碎,比如把每一帧的相机坐标都当 State 上报,这会导致数据碎片化,CLI 聚合分析时直接卡死。

代码片段

1. 命令行无感回溯收集

场景:测试员反馈玩了 3 小时后卡顿,你直接让他跑这行命令拿到 trace。

# 收集过去 5 小时的系统级 Metal 性能回溯数据
metalperftrace collect /tmp --last 5h

# 或者指定精确的时间段,方便和玩家反馈的时间点对齐
metalperftrace collect /tmp \
  --start 2026-04-01T09:41:00 \
  --end 2026-04-01T12:41:00

坑:iOS 端不支持直接敲命令,必须在设备的“控制中心”添加 Performance Trace 按钮并手动触发导出。

2. 上报游戏状态上下文

场景:在切换关卡或调整画质时,将状态同步给系统,让掉帧数据有迹可循。

import StateReporting

// 定义一个画质设置的域
let graphicsDomain = "com.mygame.graphics"
let reporter = SRStateReporter(forDomain: graphicsDomain)

// 切换到高画质,带入稳定的元数据(如是否开启光追)
reporter.reportTransition(
    toStateLabel: "High",
    stableMetadata: ["raytracing": 1, "shadow": "ultra"],
    volatileMetadata: nil
)

// 游戏中实时更新的易变数据(如当前帧的 GPU 负载等级)
reporter.reportVolatileMetadataUpdate(["gpu_load_level": 3])

坑:volatileMetadata 更新频率不要超过每帧一次,且不要塞入大字典,否则 StateReporting 自身的序列化开销会反噬游戏性能。

3. 按状态聚合分析性能

场景:用 CLI 快速看“高画质”和“中画质”下的平均帧率差异,不用打开沉重的 Instruments。

# 查看包含状态转换的详细 overview
metalperftrace overview /tmp/MyGameTrace.atrc --include-state-transitions

# 直接按特定域和状态聚合指标,一秒看出高画质下的帧率表现
metalperftrace overview /tmp/MyGameTrace.atrc --aggregate \
  --domain com.mygame.graphics \
  --state-label "High"

坑:--aggregate 输出的 JSON 格式非常适合喂给 Python 脚本或 AI 做自动化分析,但前提是你在代码里规范使用了 StateReporting,否则聚合出来的数据毫无意义。

最佳实践

  • 已有项目:立刻在 macOS 27 / iOS 27 测试机上开启 Lookback Collection。优先把游戏的核心状态机(如 Loading、Matchmaking、InGame)接入 StateReporting,不用一开始就追求细粒度。
  • 新项目:把 StateReporting 作为性能基建的一部分,和 Gameplay 状态强绑定。发版后利用 MetricKit 收集野外数据时,直接按 StateReporting 的 Domain 细分,精准定位是哪个关卡导致了线上 Crash 或掉帧。
  • 避坑指南:千万别把 StateReporting 当成普通的 Log 工具。State 应该是“持续一段时间的场景”,而不是“瞬间触发的事件”。如果你上报了 100 个只持续 10 毫秒的 State,Instruments 的时间轴会直接变成一团黑线。

还有什么值得关注

  • MetricKit 支持 StateReporting 细分:发版后的野外帧率数据(MXMetric)现在可以按你自定义的游戏状态切分,终于能知道线上玩家到底是在哪个关卡卡退了。
  • CLI 拥抱 AI 与自动化metalperftrace 支持导出 JSON,这意味着你可以把性能 trace 直接喂给 LLM 或接入 CI/CD 流水线做自动回归。
  • Metal Performance HUD 增强:HUD 现在不仅能看 FPS,还能直接显示当前 StateReporting 的状态标签,开发时连 CLI 都不用敲就能肉眼对齐状态和性能。
Metal Instruments StateReporting metalperftrace MetricKit