用Unity Frame Timing Manager检测性能瓶颈

来源:Unity官方平台


要想做出一款能流畅运行在各类设备和平台上的优秀游戏,并不简单。因此我们一直致力于完善工具、帮助开发者优化整个创作流程,比如 Frame Timing Manager。

在 Unity 2022.1 编辑器中,Frame Timing Manager 功能带来了更强的平台支持,让用户能够收集比以往更多的数据。


Frame Timing Manager能做些什么?

Frame Timing Manager 是一种能检测每一帧数据活动的功能,比如总的 CPU 和 GPU 帧时间。与更为通用的 Unity Profiler 和 Profiler API 相比,Frame Timing Manager 是一个更为专门的工具,会消耗更少的性能。它只搜集最为重要的帧数据,因此所有信息都经过了精心筛选。

Frame Timing Manager 的一个主要作用是更深入地调查性能瓶颈。让用户能够找出制约了应用性能的因素:它究竟依赖着 CPU 上的主线程或渲染线程,还是依赖于 GPU?根据分析结果,用户就能采取进一步的行动来提高性能。

用户可以用动态分辨率功能来解决 GPU 上遇到的瓶颈。或者,也能通过提高或降低渲染分辨率,动态地控制 GPU 的工作负荷。

在开发过程中,用户甚至可以在应用的 HUD 上显示帧时间,直接创建一个实时的简略型迷你分析器。这样,用户就可以随时监测帧的活动。

最后,用户可以使用 Frame Timing Manager 来生成发布版的性能报告,让应用将收集到的信息发送到服务器上,包括各平台上的性能统计等,以便更好地进行整体决策。

Frame Timing Manager API
可以测量哪些指标?

Frame Timing Manager API 的 FrameTiming 架构可提供一组实用的 CPU 和 GPU 帧测量指标。他们包括:

  • cpuFrameTime 指的是总 CPU 帧时间,是主线程某帧的开始到下一帧开始的时间间隔。
  • cpuMainThreadFrameTime 是主线程的工作时间,或者说是从帧开始到所有主线程任务完成的总耗时。
  • cpuRenderThreadFrameTime 指的是渲染线程的工作时间,或者说是从渲染线程受到的第一个任务请求到 Present() 函数被调用的总时间间隔。
  • cpuMainThreadPresentWaitTime 是 CPU 在某帧上等待 Present() 执行完成的时间。
  • gpuFrameTime 是 GPU 的工作时间,或者说是从 GPU 收到任务到 GPU 发出完成信号的总时间间隔。API 的相关限制可在下文的“支持平台与限制”一节中找到。

用Unity Frame Timing Manager检测性能瓶颈

cpuMainThreadPresentWaitTime 是“[wait]”字段出现(调用)的次数总和,包括等待 Present() 完成和达成目标 fps 所须的调用。GPU 的工作时间统计起来会比较困难,因为 GPU 通常会在“场景渲染”期间开始工作,在上一帧与下一帧的某个固定的时间点上结束。


如何开始

首先,我们需要知道 Frame Timing Manager 在开发版中是始终启用的。如果用户只打算在开发中使用它,就不必采取额外的步骤,可以直接使用工具的 C# API 及其测量器。

而如果用户想在发布版中使用该功能,就必须激活它。这里有很多种方法。一种比较直接的方法是勾选 Project Player 设置中的选框。这时,用户可以用 C# API 来读取数据。不过,这种方法的效率最低。如果直接在设置中启用该功能,它会始终保持启用状态,无论用户是否需要。

用Unity Frame Timing Manager检测性能瓶颈

using Unity.Profiling; using UnityEngine; public class ExampleScript :MonoBehaviour { FrameTiming[] m_FrameTimings = new FrameTiming[10]; void Update() { // Instruct FrameTimingManager to collect and cache information FrameTimingManager.CaptureFrameTimings(); // 读取前N张帧的缓存信息(例中为10张) // 返回值会显示实际返回了多少张样本 var ret = FrameTimingManager.GetLatestTimings((uint)m_FrameTimings.Length, m_FrameTimings); if (ret > 0) { // 在这里插入你的代码 }}}

或者,用户也可以使用 Profiler Recorder API 读取 Frame Timing Manager 的数值。Profiler Recorder API 的优点在于,Frame Timing Manager 只有在测量器被添加记录器时才会开始测量,让用户能动态地开关功能、控制性能开销。

using Unity.Profiling; using UnityEngine; public class ExampleScript :MonoBehaviour { ProfilerRecorder mainThreadTimeRecorder; void OnEnable() { // 创建ProfilerRecorder并将其添加到测量器上 mainThreadTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "CPU Main Thread Frame Time"); } void OnDisable() { // 记录器在使用后必须被处理 mainThreadTimeRecorder.Dispose(); } void Update() { var frameTime = mainThreadTimeRecorder.LastValue; // 在这里插入你的代码 }}

性能瓶颈检测

Frame Timing Manager 提供的数据可用于检测性能瓶颈。在最简单的应用中,用户可以将 CPU 主线程、CPU 渲染线程、Present Wait 和 GPU 时间进行比较,找出哪一块最有可能限制了帧率。例如:

using Unity.Profiling; using UnityEngine; public class ExampleScript :MonoBehaviour { internal enum PerformanceBottleneck { Indeterminate, // Cannot be determined PresentLimited, // Limited by presentation (vsync or framerate cap) CPU, // Limited by CPU (main and/or render thread) GPU, // Limited by GPU Balanced, // Limited by both CPU and GPU, i. well balanced }FrameTiming[] m_FrameTimings = new FrameTiming[1]; void Update() { FrameTimingManager.CaptureFrameTimings(); var ret = FrameTimingManager.GetLatestTimings((uint)m_FrameTimings.Length, m_FrameTimings); if (ret > 0) { var bottleneck = DetermineBottleneck(m_FrameTimings[0]); // Your code logic here }} static PerformanceBottleneck DetermineBottleneck(FrameTimeSample s) { const float kNearFullFrameTimeThresholdPercent = 0.2f; const float kNonZeroPresentWaitTimeMs = 0.5f; // 如果所在的平台不支持GPU计时 if (s.GPUFrameTime == 0) return PerformanceBottleneck.Indeterminate; float fullFrameTimeWithMargin = (1f - kNearFullFrameTimeThresholdPercent) * s.FullFrameTime; // GPU时间趋近于帧时间,CPU时间差异较大时 if (s.GPUFrameTime > fullFrameTimeWithMargin && s.MainThreadCPUFrameTime < fullFrameTimeWithMargin && s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin) return PerformanceBottleneck.GPU; // CPU时间趋近于帧时间,GPU时间差异较大时 if (s.GPUFrameTime < fullFrameTimeWithMargin && (s.MainThreadCPUFrameTime > fullFrameTimeWithMargin || s.RenderThreadCPUFrameTime )RenderThreadCPUFrameTime > fullFrameTimeWithMargin)) return PerformanceBottleneck.CPU; // Main thread waited due to Vsync or target frame rate if (s. MainThreadCPUPresentWait.MainThreadCPUPresentWaitTime > kNonZeroPresentWaitTimeMs) { // 所有时间都与帧时间有较大出入时 if (s.GPUFrameTime < fullFrameTimeWithMargin && s.MainThreadCPUFrameTime < fullFrameTimeWithMargin && s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin) return PerformanceBottleneck.PresentLimited; } return PerformanceBottleneck.Balanced; }}

HUD面板

Frame Timing Manager 可用作一个简单的屏显分析器,方便评估应用的健康状况。下方是一种最基本的应用形式:

using System; using UnityEngine; using Unity.Profiling; public class FrameTimingsHUDDisplay : MonoBehaviour { GUIStyle m_Style; readonly FrameTiming[] m_FrameTimings = new FrameTiming[1]; void Awake() { m_Style = new GUIStyle(); m_Style.fontSize = 15; m_Style.normal.textColor = Color.white; } void OnGUI() { CaptureTimings(); var reportMsg = $"\nCPU:{m_FrameTimings[0].cpuFrameTime :00.00}" + $"/nMain Thread:{m_FrameTimings[0].cpuMainThreadFrameTime:00.00}" + $"nRender Thread:{m_FrameTimings[0].cpuRenderThreadFrameTime:00.00}" + $"nGPU:{m_FrameTimings[0].gpuFrameTime:00.00}"; var oldColor = GUI.color; GUI.color = new Color(1, 1, 1, 1); float w = 300, h = 210; GUILayout.BeginArea(new Rect(32, 50, w, h), "Frame Stats", GUI.skin.window); GUILayout.Label(reportMsg, m_Style); GUILayout.EndArea(); GUI.color = oldColor; } private void CaptureTimings() { FrameTimingManager.CaptureFrameTimings(); FrameTimingManager.GetLatestTimings(m_FrameTimings.Length, m_FrameTimings); }}

支持平台与限制

Frame Timing Manager 支持所有 Unity 支持的平台,但有以下例外:

  • 在 Linux 平台运行 OpenGL API 时,不支持记录 GPU 时间。
  • 不支持 WebGL 平台的 GPU 时间。
  • 在 iOS 和 macOS 运行的 Metal API 时,GPU 时间在高负荷下有可能会多于总帧时间。

Frame Timing Manager 的应用有几个重要的细节:

Frame Timing Manager 生成的报告会有固定四帧的延迟。所有统计结果都是四帧之前的结果(而非当前帧)。Frame Timing Manager 支持同步记录某一帧的 CPU 和 GPU 时间。但由于平台和硬件的限制,大多数平台并不能立即提供 GPU 时间。

因此工具并不保证能统计所有帧的 GPU 时间。GPU 有可能无法按时返回结果,或者根本就不返回结果。这时,GPU Frame Time 将被记录为零。

在不支持生成 GPU 时间戳的平台上,Unity 会计算完整的 Frame Complete Time(帧完成时间),而不会测量 GPU 时间。具体来说,Unity 计算的 Frame Complete Time 是 First Submit Timestamp 加 GPU 时间的总和。如果 GPU 不能提供 GPU 时间,则 Frame Complete Time 将被自动设置为 Present Timestamp(当前时间戳)。

如果 GPU 采用了以图块为基础的延迟渲染架构,类似移动平台,则报告的结果并不会那么精确,因为 GPU 会延迟执行渲染,且渲染可能会单独进行。这时 Frame Timing Manager 就只能测量总的处理时间了。


高级主题

高级用户可使用 Frame Timing Manager 的时间戳用于帧时间轴的可视化或计算其他指标的变化值。

可用的时间戳有:

  • frameStartTimestamp:某帧开始渲染时的 CPU 时钟时间
  • firstSubmitTimestamp:某帧内渲染任务首次提交给 GPU 时的 CPU 时钟时间(视平台和 API 不同而不同);不同的平台会提交不同的时间。
  • cpuTimePresentCalled:某帧在调用 Present() 时的 CPU 时钟时间。Unity 会在这时完成渲染对象的提交,并通知 GPU 这一帧已经可以展示。
  • cpuTimeFrameComplete:GPU 完成渲染帧时的 CPU 时钟时间。在大多数平台上,这个值是 First Submit Timestamp(首次提交时间戳)加 Frame GPU 时间得出来的。

用Unity Frame Timing Manager检测性能瓶颈

希望这些改进能帮助你准确测量和了解应用的性能。这些更新目前都可在 Unity 2022.1 中找到,欢迎下载。

Unity 2022.1 下载:
https://unity.cn/releases


本文转自:Unity官方平台,转载此文目的在于传递更多信息,版权归原作者所有。如不支持转载,请联系小编demi@eetrend.com删除。

最新文章