mcpField
S
scenario-unity-performance

scenario-unity-performance

github.com/scenario-labs开发1.2k269 次安装1 天前更新
代码仓库

关于 scenario-unity-performance

面向 Unity 性能工程的技能,通过 Editor 分析、开发构建和性能捕获定位瓶颈,并用目标设备上的前后数据验证优化。覆盖脚本、渲染、内存、加载卡顿与构建体积。

主要功能

  1. 01建立预算并记录基准数据
  2. 02分析开发构建中的帧与性能捕获
  3. 03归因脚本、渲染和内存开销
  4. 04检查卡顿、GC 分配与 GPU 负载
  5. 05逐项验证优化并守护性能预算

适用场景

  1. 01定位目标设备上的 CPU 或 GPU 瓶颈
  2. 02调查加载、生成或着色器编译造成的卡顿
  3. 03减少每帧分配并检查内存预算
  4. 04对比优化前后的帧时间和构建体积

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/scenario-labs/skills --skill scenario-unity-performance

安装后在 Claude Code 中输入/scenario-unity-performance确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
critique.md
expert-notes.md
gui-paths.md
procedures.md
sources.md
scripts/
ut_perf.py
scripts/AgentKit/Performance/
PerfAudit.cs
PerfBuild.cs
PerfCapture.cs
PerfFrameDebug.cs
PerfLod.cs
PerfRendering.cs
PerfShaders.cs
scripts/Runtime/Performance/
AgentKit.Performance.Runtime.asmdef
AllocScope.cs
PerfProbe.cs

详细介绍

Unity 性能(性能工程师)

专家级性能工作是一门测量学科:以毫秒设定预算,在做出任何改动前明确瓶颈线程,优先解决测得的最大开销,先调整结构再进行调优,并用开发版 Player 的前后数据证明每项修复。Agent 可无界面完成所有工作:在 Editor Play 模式下使用 AgentProfile 迭代,在批处理模式的 Player 中使用 PerfProbe 得出结论,通过 PerfCapture 从 Profiler 捕获数据中归因开销,使用 metal_trace 测量 GPU 时间,使用 PerfBuild 测量大小。目标环境:Unity 6000.3.21f1、URP 17.3、macOS Apple Silicon。如果此处提到的同级技能不在可用技能中,请让用户安装 (npx skills add scenario-labs/skills --skill <name>);若无人值守,请根据工具架构继续操作并标明缺失项。

必备背景: scenario-unity-expert(通道、评审循环、6.3 陷阱、ut_* 工具包)。

立场(专家要点)

  1. 动手前先明确瓶颈。“如果你的性能分析结果是这样的,那么加快 GPU 也不会让运行速度更快”(Unity Profiler 团队的 Peter Hall,_cV1B2hqXGI [00:05:50])。观察结果:每帧移除 1.36 MB 垃圾,并未改善受 5,946 次 SetPass 调用限制的帧。GPU 瓶颈需要 GPU 计时;Frame Debugger 不提供该数据(Hall [frame 00:09:18])。
  2. **以 Player 为准,用 Editor 迭代。**权威依据是在最低目标设备上运行的开发版构建(性能分析电子书,第 18 页)。GRD:在包含 200 万个实例的压力测试中提升 4.4 倍,在 Editor 中提升 2.07 倍,而在实际游戏的独立运行版本中仅提升 1.45 倍(Oc6T4hh5gaI [frame 00:12:41]);这里的 Editor 测试结果从提升 1.5 倍到 3.0 倍不等,Player 在压力测试场景中则从提升 1.9 倍到 2.8 倍不等。预计提升约 1.5 倍。
  3. **GC.Alloc 样本时间不等于实际开销。**Unity 只记录时间戳和大小(电子书,第 27 页):54,021 次分配显示为 1.01 ms,却导致了 19.97 ms 的 GC.Collect(Hall [frame 00:21:52])。观察结果:每帧 27,000 次分配显示为 0.8 至 1.1 ms;产生分配的代码实际耗时 3.8 至 5.4 ms。统计字节数和调用次数,测量垃圾回收,目标是每个游戏帧分配 0 B,并确保探针本身不产生分配(git-amend ham_w48aRJ4 [00:11:30])。
  4. **6.3 URP 绘制调用 = SRP Batcher + GPU Resident Drawer;2024 年的建议仅适用于 Built-in。**材质上的“Enable GPU Instancing”关闭,静态批处理关闭,不使用 MaterialPropertyBlock(6.3 手册表格)。除非将 BatchRendererGroup Variants 设为 Keep All,且 URP 资源的每个渲染器都是 Forward+ 或 Deferred+,否则 GRD 会静默地不执行任何操作(根据观察得出的 URP 17.3 源码结果)。随后证明覆盖情况:GRD 绘制在 Frame Debugger 中显示为“Hybrid Batch Group”(仅限 GUI Editor);其他每次绘制都对应一个被排除的渲染器(MPB、LPPV、实时 GI、非 DOTS 着色器、蒙皮;6.3 手册);无界面运行时,请在 Start 之后逐个检查渲染器是否符合条件。
  5. **先调整数据布局;将工作分摊到多个帧只会掩盖开销。**20 万个对象:通过管理器、结构体、Burst 和 SoA,将 MonoBehaviour Update 从 27 ms 降至 0.13 ms;“这只是在把工作分摊到各处”(Jason Booth,NAVbI1HIzCE [00:06:38]-[00:18:17])。
  6. **修复卡顿前先完成归因,并在代码中保留标记。**流式加载和生成系统都应带有各自的 ProfilerMarkers 和计数器;Deep Profile 只能找出填补空白的部分(Hall [00:18:39]-[00:19:12])。编译会显示为 Shader.CreateGPUProgram(加载时预热经跟踪的 GraphicsStateCollection,Nicolas Borromeo CmD8MVGkDxQ [00:11:14]-[00:14:35]);这里的预热移除了所有 18 次首次使用时的编译,但第一帧仍在 Mono.JIT 上耗时约 250 ms。
  7. **常驻内存会造成致命影响;未跟踪内存通常不会。**0.75 GB 未跟踪内存实际有 140 MB 常驻(Borromeo [00:32:22]);Editor 的内存用量约为 Player 的 5 倍(Uuzd39AjFWQ [00:10:03])。在笔记本电脑上,页面调度和 VRAM 溢出本身就会导致卡顿。
  8. **计数准确,时间会波动[新增,基于观察]。**在共享机器上(此处负载平均值为 50 至 210),交错运行 A/B 测试,并要求结果的变化方向一致。

首先明确

确定目标平台和各档位的最低规格设备;帧预算(桌面端 16.67 ms,移动端为其 0.65 倍);分辨率和质量档位;允许变更的范围(仅代码,还是也包括美术、设计、手感);可重复的基准测试;评估流式加载时使用基于时间的测试(速度提升后,固定的每帧步长会更早到达边界)[added];脚本后端和构建类型;实际进行测量的机器(独立 GPU、接通电源;探测 JSON device)[added]。默认值:PC 档位 URP 资源、60 fps、1920x1080、用于迭代的开发版 Mono 玩家,用于控制体积的发布版 IL2CPP。

工作流

  1. 设定预算和基准测试。 GATE:基准 CSV。
  2. 在编辑器中迭代。 ut_perf.profile(render=False 用于隔离脚本)、ab_profile 用于测量时间差。GATE:数值标注为“编辑器,迭代”。
  3. 静态基线。 PerfRendering.Audit(批处理、每个渲染器的 GRD 适用性、各平台的图形作业、Build Profile、堆栈跟踪、上传缓冲区、剔除)、lint_scripts、Project Auditor(project_auditor,先处理 Major,triage_auditor 保留逐帧行)、PerfAudit.AudioImport、bundle_duplicates。GATE:按调用频率对发现的问题进行分类。
  4. 测量玩家版本。 build_player(development=True)、run_player(..., shot=png, markers=[...])。GATE:draw_calls > 0,PNG 已打开,budget_check。
  5. 判断性能瓶颈。 捕获等待标记(capture_player、analyze_capture)、最长帧与中位帧。GPU:metal_trace + metal_gpu_frames(GPU 占用情况和各渲染通道的毫秒数;在 GUI 中使用 Rendering Debugger Display Stats),然后进行像素受限测试(4K 与 1080p、渲染比例)。GATE:明确瓶颈并给出数值。
  6. 归因。 attribute_spikes(你的标记、GC.Collect、编译、MeshCollider 烹制、同步读取、物理步进)、physics_catchup、memory_budget、density_check、每个代码块的分配量(AllocScope、Is.Not.AllocatingGCMemory())、GRD 覆盖率(PerfFrameDebug.Capture 或窗口)。GATE:明确列出成本最高的三项,没有无法解释的尖峰。
  7. 每次只修复一项,优先调整结构。 脚本:分配、管理器,然后是 Burst。CPU 渲染:Material Variants、SRP Batcher 着色器、EnableGpuResidentDrawer、单个摄像机、在 DX12/Vulkan 上拆分图形作业。GPU:动态分辨率或 STP、深度预通和减少透明效果、在三角形已达到像素大小时使用 LOD、将后期处理固定在特定时间片上;光照使用 scenario-unity-rendering-lighting。卡顿:异步加载、预热对象池、在加载阶段预热 PSO、烹制后的 MeshColliders、音频加载类型、避免重复的资源包依赖。GATE:compare_line 显示目标指标下降,其他指标均未上升,图像保持一致。
  8. 发布配置。 发布版 IL2CPP,选择剥离和代码生成方式,并根据已使用的集合剥离变体(P15);通过 BuildPlayerOptions 设置标志(平台配置文件共享设置)。GATE:构建成功,体积原因明确,冒烟测试运行正常。
  9. 守护。 每次提交都运行分配测试和 Performance Testing 示例组,并对已更改的脚本运行 lint。GATE:测试通过。

数值

数值对应指标来源
16.67 / 33.33 ms;移动端为 x 0.6560 / 30 fps 下的帧预算;热余量profiling e-book 第 9、36 页
0 B每个游戏帧的托管分配量6.3 Manual;e-book 第 66 页
最高约 1 ms增量 GC 写屏障的每帧开销e-book 第 69 页
4.4x / 2.07x / 1.45xGRD 提升:200 万实例压力测试 / 在 Editor 中运行游戏 / 独立运行同一游戏Oc6T4hh5gaI [帧 00:12:41]
217 / 199 / 176 / 161 fpsDX12 Split / Legacy / 纯 DX12 / Native graphics jobsOc6T4hh5gaI [帧 00:03:52]
256 个顶点低于此值时 GPU 实例化效率低:应改为合并6.3 Manual
约 250 ms创建一个 PSO 时的卡顿Oc6T4hh5gaI [帧 00:04:48]
4 次像素着色器调用每个单像素三角形(2x2 四边形);目标为每个渲染像素绘制 1 个三角形 [已添加]CmD8MVGkDxQ [00:27:26]
48.55 / 80 ms开发帧中每帧调用 Debug.Log 的开销xjsqv8nj0cw [00:10:23]
< 200 / > 400 KB音频:Decompress On Load / Streamingconsole e-book 第 104-105 页
最高 1 ms移动端每增加一个活动摄像机的 CPU 开销console e-book 第 89 页
最高 10 sAutoconnect Profiler 增加的启动时间e-book 第 47 页
RAM 的 50% / 70% / 80%内存预算:2 GB 手机 / 通用设备 / 专用设备Hall;Unity 教程;e-book
5,946 降至 33 次 SetPass;58-60 降至 5.2-6.2 ms3,000 个球体,将 MPB 色调改为 8 个 Material Variants,开发版播放器实测
18 降至 0预热 10 个 PSO 后,首次使用时的 Shader.CreateGPUProgram(加载时为 3.6 ms)实测
177.9 / 110.5 / 116.3 MBmacOS 应用:IL2CPP Minimal + speed / IL2CPP High + size / Mono实测
1,475 万降至 26 万个三角形(每像素 7.1 降至 0.12 个)对 400 个高密度网格应用 Mesh LOD 后,绘制调用数不变实测
帧耗时 57.1 ms = Spawn.Burst 26.9 ms + 4 次编译;下一帧有 3 次物理步进突发实例化 3,000 个对象,耗时记录归于其自身标记实测
48-80 ms 对比 0 ms生成时烹制 MeshCollider,对比加载时在作业中调用 Physics.BakeMesh(40 个网格)实测
33 us 对比 2.6-3.9 us每次 Debug.Log 的开销;开发版播放器中,堆栈跟踪设为 ScriptOnly 对比 None实测
2.5 对比 5.8 ms;像素数增加 4 倍时为 1.2xGPU 忙碌时间对比 CPU 主线程时间(Metal System Trace);4K 对比 1080p:不受像素数限制实测
1,944 降至 1,002 KB两个共享纹理的资源包:隐式副本对比共享资源包实测

质量门槛

  • **可测量:**玩家 budget_check 通过(p95 低于预算,且无游戏卡顿);玩家中的 gc_check 通过;未解释的 attribute_spikes = 0;除强制尖峰外,physics_steps 至多为 1;memory_budget 通过;density_check 通过;PerfRendering.Audit 的 counts.error == 0,且所有 GRD 后续影响均有说明;加载后 Shader.CreateGPUProgram 为 0;代码中没有新增的 Major Project Auditor 问题;IL2CPP 构建在预算范围内。
  • **视觉:**玩家的最后一帧(-perfShot)和使用相同书签拍摄的编辑器前后截图、ut_review.compare,并已打开联系表;没有空白、纯色或洋红色画面。

常见错误

错误表现修正方式
优化了未受限的一侧分配减少,帧时间不变先分类(等待标记、GPU 忙碌、SetPass)
轻信 Editor 数值或单次运行“快了 3 倍”的优势在播放器中消失使用播放器 CSV;以一致的符号约定运行 ab_profile
对不渲染内容的批处理模式播放器进行基准测试帧时间为 0.3 ms,绘制调用为 0执行 PerfProbe 提交 + -perfShot
开启 GRD 后毫无变化绘制调用数不变Keep All、Forward+/Deferred+、关闭静态批处理,并修复失效项
用 MaterialPropertyBlock 设置对象颜色SetPass = 绘制调用数;StdRender.ApplyShader 占主导使用 Material Variants;搭配 GRD 使用 unity_RendererUserValue
把 GC.Alloc 的毫秒数当作开销“分配耗费了 0.5 ms”统计次数和字节数、GC.Collect 的毫秒数,并进行 A/B 测试
每帧都会格式化字符串的监视器GC 列始终不为 0使用预分配缓冲区,只写入一次(PerfProbe)
在极少执行的分支中使用闭包功能关闭时仍有分配将捕获的局部变量移入分支
用 Deep Profile 查找尖峰小函数的耗时被放大在代码中保留标记和计数器,并使用 -perfMarkers
根据 Frame Debugger 调优 GPU计时靠猜测使用 metal_trace(Windows 上使用 PIX、Nsight)
在笔记本电脑上固定渲染比例设备冷却时正常,发热或负载时出现卡顿使用动态分辨率;只有像素数上限固定时才以固定比例使用 STP
以为预热能解决首帧卡顿仍然耗时 250 ms根据捕获结果判断:JIT 表示 IL2CPP,加载表示异步
把分散帧任务当作解决方案峰值降低,总耗时不变先测量未分摊的耗时,再修正结构
在运行时添加 MeshCollider生成时出现尖峰,伴随 Physics.BakePhysXCollisionMeshData在作业中使用 Physics.BakeMesh,并预烘焙碰撞网格
通过 EditorUserBuildSettings 设置开发标记所有平台配置文件都发生改变使用 BuildPlayerOptions 或构建配置文件资源
根据 BuildReport 的 totalSize 估算大小178 MB 的应用显示为 1.9 GB查看磁盘上的实际交付文件夹
将 InstantiateAsync 当作解决卡顿的办法随后某一帧耗时 17 到 83 ms使用对象池,并在过渡期间预热
对新构建版本的首次启动进行基准测试212 与 22 ms 的差异丢弃首次启动的数据

未经批准不得更改的内容

游戏手感(固定时间步长、最大允许时间步长、动画时序、输入、AI 更新);未提供前后对比截图时,不得更改美术方向(纹理尺寸、灯光、LOD 阈值、渲染比例);不得更改流水线、引擎版本或重写 ECS;除非游戏运行时分配量为 0 B,且 A/B 测试证明有收益,否则不得关闭增量 GC;不得盲目调高 asyncUploadBufferSize(永远不会恢复原值,控制台电子书第 31 页);发布版本不得关闭 VSync;前后对比之间不得更改基准测试路径。

交接

  • 接收: 来自 scenario-unity-expert 的数字化简报;来自 scenario-unity-architecture 和 scenario-unity-gameplay 的系统代码;来自 scenario-unity-rendering-lighting 的流水线资源;来自 scenario-unity-shaders 的着色器;来自 scenario-unity-vfx 的效果;来自 scenario-unity-ui 的 UI;来自 scenario-unity-pipeline-automation 的 AssetBundle 和 Addressables。
  • 交付: 已归因的成本、前后对比表、CSV、跟踪记录、PNG:将变体剥离结果交给 scenario-unity-shaders;将灯光、阴影和后期处理裁剪结果交给 scenario-unity-rendering-lighting;将过度绘制预算交给 scenario-unity-vfx 和 scenario-unity-ui;将物理设置交给 scenario-unity-gameplay;将重复项修复、CI 性能测试和构建配置交给 scenario-unity-pipeline-automation;将设备和热验证结果交给 scenario-unity-mobile;将 Web 限制交给 scenario-unity-web。

Unity 6.3 说明

  • SRP Batcher + GRD + GPU 遮挡(17.3 URP 资源包含遮挡标志);动态批处理在 6.5 中弃用,在 6.6 中废弃。
  • 在 DX12/Vulkan/主机平台上,拆分图形作业为默认设置(Oc6T4hh5gaI [00:03:04]);6.3 URP 模板中的设置为:Windows 开启/拆分、Android 关闭/原生、Metal 关闭/原生;各平台的 getter 为内部接口(观察所得)。
  • GraphicsStateCollection 为实验性功能,仅在开发版播放器中记录跟踪;WebGPU 跟踪功能将在 6.4 中提供。FrameDebuggerUtility 为内部接口。
  • Profile Analyzer 1.4.0、Memory Profiler 1.1.12、Project Auditor 1.0.2(从 6.4 起内置)、Performance Testing 3.5.0(从 6.6 起为核心组件)以及 Profiling Core 1.0.3(ProfilerCounterValue 所需)均为按名称添加的预安装包。
  • Mesh LOD 支持 6.2 及更高版本。Physics.BakeMesh(int, bool) 已弃用:请传入 mesh.GetEntityId()。6.3 使用 Boehm(非分代式);CoreCLR 尚待后续实现。

参考资料

评论

支持 Markdown