编辑器里的帧时间改善了,玩家设备上的游戏却未必更快。比较数字之前,先确定它们来自什么构建、什么负载。
判断发布性能,应在目标硬件上运行接近正式发布的构建。需要查找耗时原因时,换用具备诊断功能的构建;修复后,再回到接近发布的条件验证。本文把这种保留少量测量代码的构建称为测量构建。这是项目可以采用的约定,并不是三个引擎共有的构建配置名称。
根据问题选择构建
编辑器性能分析适合在开发中查找耗时脚本、资源处理和内存分配。带有诊断功能的独立构建(如 Unity 或 Unreal Engine 的 Development 构建)能排除编辑器进程的负载,并在目标设备上进行详细分析。但这些结果不能直接当作发布构建的性能。
记录结果时附上构建配置。配置变化可能同时改变开销和被测行为。
编辑器窗口、资源处理和其他工具会共享设备资源。诊断构建还可能包含额外代码和性能分析开销。差异取决于引擎、后端、平台和启用的选项,没有一个固定数值可以直接扣除。在诊断构建中找到的高开销函数仍然值得调查,只是需要回到玩家实际运行的构建验证其影响。
改代码之前,先固定运行条件
单靠构建设置无法保证比较可复现。先选一个场景,例如读取存档后沿固定路线移动摄像机,并为每次结果记录以下条件:
- 构建 ID、提交、引擎版本、脚本后端和构建配置
- 设备、GPU 驱动、分辨率、画质设置、VSync 和帧率上限
- 场景、初始状态、输入或路线、测量时长或帧数
- 预热规则和缓存状态,尤其是着色器编译与首次加载的处理方式
- 启用的测量代码、采样间隔,以及缺失或丢弃的样本
先把未修改的基线多跑几次,再测试候选构建。如果基线之间的波动已经接近预期收益,应先检查测试条件。重复运行有助于发现波动,但仅靠重复运行还不能证明结果在统计上可靠。手机和笔记本电脑还要保持相近的供电与温度条件。
首次加载与预热后的游戏过程应分开测试。测量稳定状态时,排除着色器编译可能是合理的;如果目标是调查首次启动卡顿,同样的处理却会把问题本身排除掉。
配置测量构建
Unreal Engine:引擎支持时使用 Test
Epic 将 Test 定义为保留部分控制台命令、统计和性能分析工具的 Shipping 配置。需要发布级优化和这些诊断工具时,可以从 Test 开始。Development 也启用了大部分优化,不能把它等同于未优化的 Debug 构建。
先检查引擎安装。Installed Build 默认包含 Shipping、Development 和 DebugGame。Test 通常需要源码构建的引擎,或包含 Test 的自定义 Installed Build。在命令中选择 Test,不会补上缺少的二进制文件。
支持 Test 的引擎可参考以下 Windows 打包命令。替换项目路径,并使用安装目录中的 Automation Tool 启动脚本:
RunUAT.bat BuildCookRun -project="C:/Path/MyGame.uproject" -platform=Win64 -clientconfig=Test -cook -stage -pak -package -build
在实际生成的包中确认所需命令和计数器是否可用。不要假设 UE_LOG 一定存在:Test 和 Shipping 默认会在编译时移除日志,更改该设置可能需要重新构建引擎。测量代码应限于项目中必要的位置。如果只能用 Development 调查,最后仍应使用项目支持的测量手段在 Shipping 中验证。
Unity:后端和编译设置与发布版本一致
在 Unity 6 中建立专用的构建配置文件,进行接近发布环境的比较时关闭 Development Build。产品使用 IL2CPP,就测 IL2CPP;使用 Mono,就保留 Mono。IL2CPP 的 Release 或 Master 配置也应与产品一致。比较代码修改时同时更换后端,会引入另一个变量。
为 Unity Profiler 保留独立的 Development 配置。连接 Profiler 和 Deep Profiling 是诊断选项,不应在发布性能比较中被无意保留。Unity 6 之前的版本可以用 BuildPipeline 脚本维持同样的区分。
可以使用配置专属的脚本定义符号,让诊断标记只在测量构建中生效:
using System.Diagnostics;
public static class Measure
{
[Conditional("MEASUREMENT")]
public static void Mark(string label)
{
UnityEngine.Debug.Log($"[measure] {label} @ {UnityEngine.Time.frameCount}");
}
}
在配置的 Scripting Defines 中加入 MEASUREMENT,然后在测试场景的开始、结束等位置调用 Measure.Mark。这是日志标记,不是帧时间采集器。不要每帧调用:字符串格式化和日志写入也有开销。没有该符号时,[Conditional] 会移除调用点,但不会从程序集中移除方法体。
Godot:发布导出加测量功能标签
复制导出预设,关闭 Export With Debug。渲染器、平台和导出模板应与计划发布的版本一致。仅为创建测量预设,无需编译自定义引擎模板。
在预设中添加 measurement 等自定义功能:
custom_features="measurement"
C# 项目可在测试场景开始处读取标签:
if (OS.HasFeature("measurement"))
{
GD.Print($"[measure] scenario-start frame={Engine.GetFramesDrawn()}");
}
自定义功能标签只对导出的项目生效,普通的编辑器运行不会使用它们。请检查导出的可执行文件。和 Unity 示例一样,这段代码仅标记场景位置,指标需要单独采集;采集开销和设置也应在各次运行中保持一致。
按实际采样粒度解读指标
60 FPS 的帧预算约为 16.67 ms,30 FPS 约为 33.33 ms。除了平均 FPS,还要检查帧时间分布。P95 是 95% 的观测值不超过的数值。更罕见的严重卡顿仍可能被隐藏,因此还应按需查看更高百分位、超出帧预算的次数和时间线。
先确认统计对象。定期上报的遥测样本算出的 P95,是这些样本的 P95,不一定代表每一个渲染帧。比较两个报告前,应记录采样方式。
如果问题与位置有关,附上场景、坐标和摄像机方向。“站在门口、朝向物体密集的房间时出现慢帧”比整段会话的平均值更有助于复现,但还不能确定是哪个对象或系统造成了开销。
找到原因后,重跑原来的测试
按记录的条件复现问题,再用 Unity Profiler、Memory Profiler、Unreal Insights 或 Godot 的性能分析器调查。Godot 内置脚本性能分析器面向 GDScript;深入分析 C# 代码时,需要合适的 .NET 性能分析器。
修改一个疑似开销来源后,在测量构建中重跑原来的场景。确认多次运行仍有改善,并检查内存、加载或其他帧内工作是否恶化。诊断跟踪变快只是中间结果。
同样的数据也能帮助决定是否增加特效或对象。但渲染线程有余量,不代表 GPU 时间和内存也有余量。加入内容后,仍需在相同的目标设备条件下测量。
用 Framedash 保留可检查的比较结果
Framedash 的仪表板热力图可以在地图上查看已采集的性能样本,并按构建、平台和已记录的属性筛选。各引擎的 SDK 设置与可用采集器不同,请参阅 Unity、UE5 和 Godot C#。使用图表前,先确认目标构建实际发送了哪些指标。
Unity 用户可以从性能运行比较试点开始:在自己的 PC 上采集基线、未修改的重复运行和候选运行,再检查条件与帧间隔摘要,不需要地图。这些间隔测的是 SDK Update 回调之间的时间,不是 GPU 完成或画面呈现时间。试点会说明证据是否可比较,不会自动判定性能回归合格与否。
先选一个可复现的场景和一个待验证的修改。把基线结果、运行条件和候选结果放在一起,下一次才能继续回答同一个问题。