您想找什么?
Hero background image

游戏开发者的性能分析技巧

为方便起见,此网页已进行机器翻译。我们无法保证翻译内容的准确性或可靠性。如果您对翻译内容的准确性有疑问,请参阅此网页的官方英文版本。

流畅的性能对于为玩家创造沉浸式游戏体验至关重要。为了确保您的游戏经过优化,一致的端到端性能分析工作流程是高效游戏开发的“必备条件”,它始于一个简单的三点程序:

  • 在进行重大更改之前进行分析:建立基准。
  • 在开发过程中进行分析:跟踪并确保更改不会破坏性能或预算。
  • 之后进行分析:证明更改达到了预期效果。

本页面概述了游戏开发者的一般性能分析工作流程。它摘自电子书,终极Unity游戏性能分析指南, 可免费下载(Unity 6版本的指南将很快提供)。这本电子书由外部和内部的Unity游戏开发、性能分析和优化专家共同创建。

在本文中,您可以了解与性能分析相关的有用目标、常见的性能瓶颈,例如CPU受限或GPU受限,以及如何更详细地识别和调查这些情况。

FPS帧率图

设定帧预算

玩家通常使用帧率或每秒帧数(fps)来衡量性能,但作为开发者,通常建议使用帧时间(毫秒)。考虑以下简化场景:

在运行时,您的游戏在0.75秒内渲染了59帧。然而,下一帧需要0.25秒来渲染。平均交付帧率为60 fps听起来不错,但实际上玩家会注意到卡顿效果,因为最后一帧需要四分之一秒来渲染。

这就是为什么每帧的时间预算很重要的原因之一。这为您在分析和优化游戏时提供了一个坚实的目标,最终为您的玩家创造了更流畅和更一致的体验。

每帧将根据您的目标fps有一个时间预算。一个目标为30 fps的应用程序每帧应始终少于33.33毫秒(1000毫秒/30 fps)。同样,目标为60 fps则每帧留有16.66毫秒(1000毫秒/60 fps)。

您可以在非交互序列中超出此预算,例如在显示UI菜单或场景加载时,但在游戏过程中不能超出。即使是单个超出目标帧预算的帧也会导致卡顿。

注意:在VR游戏中保持一致的高帧率对于避免导致玩家恶心或不适至关重要,并且通常是您的游戏获得平台持有者认证所必需的。

每秒帧数:一个具有误导性的指标

玩家衡量性能的常见方式是使用帧率或每秒帧数。然而,建议您使用毫秒的帧时间。要理解原因,请查看上面的 fps 与帧时间的图表。

考虑这些数字:

1000 毫秒/秒 / 900 fps = 每帧 1.111 毫秒

1000 毫秒/秒 / 450 fps = 每帧 2.222 毫秒

1000 毫秒/秒 / 60 fps = 每帧 16.666 毫秒

1000 毫秒/秒 / 56.25 fps = 每帧 17.777 毫秒

如果您的应用程序以 900 fps 运行,这意味着每帧的时间为 1.111 毫秒。在 450 fps 时,每帧为 2.222 毫秒。这仅代表每帧 1.111 毫秒的差异,即使帧速率似乎下降了一半。

如果您查看 60 fps 和 56.25 fps 之间的差异,分别为每帧 16.666 毫秒和 17.777 毫秒。这也代表每帧多出 1.111 毫秒,但在这里,帧速率的下降在百分比上感觉远不那么戏剧化。

这就是为什么开发人员使用平均帧时间来基准游戏速度而不是 fps。

除非您低于目标帧速率,否则不要担心 fps。专注于帧时间来衡量您的游戏运行速度,然后保持在您的帧预算内。

阅读原始文章“罗伯特·邓洛普的 fps 与帧时间”以获取更多信息。

移动挑战

移动挑战

热控制是开发移动设备应用程序时需要优化的最重要领域之一。如果 CPU 或 GPU 由于代码效率低下而长时间全速工作,这些芯片会变热。为了避免过热和可能对芯片造成损坏,操作系统将降低设备的时钟速度以使其冷却,从而导致帧卡顿和糟糕的用户体验。这种性能降低被称为热节流。

更高的帧速率和增加的代码执行(或 DRAM 访问操作)会导致电池消耗增加和热量产生。糟糕的性能也可能使您的游戏在低端移动设备的整个部分上无法玩,这可能导致错失市场机会。

在处理热量问题时,请考虑您可以使用的预算作为系统范围的预算。

通过早期分析来优化您的游戏,以应对热量限制和电池耗尽。根据目标平台硬件调整项目设置,以应对热量和电池耗尽问题。

在移动设备上调整帧预算

应对设备热量问题的一个一般建议是在长时间游戏中留出大约35%的帧空闲时间。这给移动芯片提供了冷却的时间,并有助于防止过度电池耗尽。使用每帧33.33毫秒的目标帧时间(对于30 fps),移动设备的帧预算将大约为每帧22毫秒。

计算如下:(1000 ms / 30) * 0.65 = 21.66 ms

要在移动设备上实现60 fps,使用相同的计算需要目标帧时间为(1000毫秒/60)*0.65=10.83毫秒。在许多移动设备上实现这一点是困难的,并且会使电池耗尽速度是目标30 fps的两倍。出于这些原因,许多移动游戏的目标是30 fps而不是60。使用Application.targetFrameRate来控制此设置,并参考"设置帧预算"部分在分析电子书中以获取有关帧时间的更多详细信息。

移动芯片上的频率缩放可能使在分析时识别帧空闲时间预算分配变得棘手。您的改进和优化可能会产生净积极效果,但移动设备可能会降低频率,因此运行更凉爽。使用自定义工具,如FTracePerfetto来监控移动芯片频率、空闲时间和缩放,在优化之前和之后。

只要您保持在目标fps的总帧时间预算内(例如30 fps的33.33毫秒),并看到您的设备工作更少或记录较低的温度以维持此帧率,那么您就走在正确的轨道上。

在移动设备上增加帧预算的另一个原因是考虑到现实世界的温度波动。在炎热的日子里,移动设备会升温并难以散热,这可能导致热量限制和糟糕的游戏性能。将一部分框架预算留出,以帮助避免这种情况。

减少内存访问操作

减少内存访问操作

在移动设备上,DRAM访问通常是一个耗电的操作。Arm的针对移动设备图形内容的优化建议指出,LPDDR4内存访问的成本大约为每字节100皮焦耳。

通过以下方式减少每帧的内存访问操作数量:

  • 降低帧率
  • 在可能的情况下降低显示分辨率
  • 使用更简单的网格,减少顶点数量和属性精度
  • 使用纹理压缩和mipmap

当您需要关注利用Arm CPU或GPU硬件的设备时,Arm Performance Studio工具(特别是Streamline Performance Analyzer)包括一些很好的性能计数器,用于识别内存带宽问题。可用的计数器在相应的用户指南中列出并解释,例如,Mali-G710性能计数器参考指南。请注意,Arm Performance Studio GPU分析需要Arm Immortalis或Mali GPU。

建立基准测试的硬件层级

除了使用特定平台的分析工具外,为每个平台和您希望支持的质量层级建立层级或最低规格设备,然后对这些规格进行性能分析和优化。

例如,如果您针对移动平台,您可能决定支持三个层级,质量控制根据目标硬件切换功能。然后,您针对每个层级中的最低设备规格进行优化。另一个例子是,如果您正在为控制台开发游戏,请确保在旧版本和新版本上进行分析。

我们的最新移动优化指南提供了许多技巧,可以帮助您减少热限制并延长运行您游戏的移动设备的电池寿命。

从高到低级别的性能分析

在分析时,您要确保将时间和精力集中在可以产生最大影响的领域。因此,建议在分析时采用自上而下的方法,这意味着您从渲染、脚本、物理和垃圾收集(GC)分配等类别的高层次概述开始。一旦您确定了关注领域,就可以深入到更详细的内容中。使用此高级通行证收集数据并记录最关键的性能问题,包括导致不必要的托管分配或核心游戏循环中过度 CPU 使用的场景。

您需要首先收集 GC.Alloc 标记的调用堆栈。如果您对这个过程不熟悉,请在电子书中查找标题为“定位应用程序生命周期中的重复内存分配”的部分中的一些提示和技巧。

如果报告的调用堆栈不够详细,无法追踪分配或其他减速的来源,您可以执行第二次分析会话,并启用深度分析,以找到分配的来源。我们在电子书中更详细地介绍了深度分析,但总的来说,它是分析器中的一种模式,捕获每个函数调用的详细性能数据,提供对执行时间和行为的细致洞察,但与标准分析相比,开销显著更高。

在收集帧时间“罪犯”的笔记时,请务必注意它们与帧的其余部分的相对比较。当启用深度分析时,这种相对影响可能会扭曲,因为深度分析通过对每个方法调用进行插桩而增加了显著的开销。

早期分析

虽然您应该在项目的整个开发周期中始终进行分析,但在早期阶段开始分析时,分析带来的最显著收益是最大的。

早期并频繁地进行分析,以便您和您的团队理解并记住项目的“性能特征”,以便进行基准测试。如果性能急剧下降,您将能够轻松发现问题出现的时刻并修复问题。

虽然在编辑器中进行分析为您提供了一种轻松识别主要问题的方法,但最准确的分析结果总是来自于在目标设备上运行和分析构建,同时利用特定平台的工具深入挖掘每个平台的硬件特性。这种组合将为您提供跨所有目标设备的应用程序性能的整体视图。例如,您可能在某些移动设备上受限于 GPU,但在其他设备上受限于 CPU,您只能通过在这些设备上进行测量来了解这一点。

性能分析流程图

识别性能问题

下载此图表的可打印 PDF 版本 在这里

分析的目的是识别瓶颈作为优化的目标。如果您依赖猜测,可能会最终优化游戏中不是瓶颈的部分,从而导致整体性能几乎没有改善。一些“优化”甚至可能会恶化您游戏的整体性能,而其他一些可能需要大量劳动但结果微不足道。关键是优化您集中时间投资的影响。

上面的流程图说明了初始分析过程,后面的部分提供了每个步骤的详细信息。他们还展示了来自真实Unity项目的Profiler捕获,以说明需要关注的内容。

要全面了解所有CPU活动,包括何时等待GPU,请在Profiler的CPU模块中使用时间线视图。熟悉常见Profiler标记以正确解读捕获。某些Profiler标记可能会根据目标平台的不同而有所不同,因此请花时间探索您游戏在每个目标平台上的捕获,以了解您项目的“正常”捕获是什么样的。

项目的性能受限于耗时最长的芯片和/或线程。这就是优化工作应集中关注的领域。例如,想象一下一个目标帧时间预算为33.33毫秒且启用了VSync的游戏的以下场景:

  • 如果CPU帧时间(不包括VSync)为25毫秒,GPU时间为20毫秒,那就没问题!您受限于CPU,但一切都在预算之内,优化不会提高帧率(除非您将CPU和GPU都降到16.66毫秒以下,并跳到60 fps)。
  • 如果CPU帧时间为40毫秒,GPU为20毫秒,您受限于CPU,需要优化CPU性能。优化GPU性能无济于事;实际上,您可能希望将一些CPU工作转移到GPU上,例如,在适用的情况下使用计算着色器而不是C#代码,以平衡工作负载。
  • 如果CPU帧时间为20毫秒,GPU为40毫秒,您受限于GPU,需要优化GPU工作。
  • 如果CPU和GPU都为40毫秒,您受限于两者,需要将两者都优化到33.33毫秒以下,以达到30 fps。

请参阅这些资源,进一步探讨CPU或GPU受限的情况:

您保持在帧预算之内吗?

您保持在帧预算之内吗?

在开发过程中早期和频繁地对项目进行分析和优化将帮助您确保应用程序的所有CPU线程和整体GPU帧时间都在帧预算之内。指导这一过程的问题是,您是否在帧预算之内?

上面是一个来自Unity移动游戏的Profiler捕获图像,该游戏由一个持续进行分析和优化的团队开发。该游戏在高规格手机上目标为60 fps,在中低规格手机上目标为30 fps,例如此捕获中的手机。

注意在选定帧上,几乎一半的时间被黄色WaitForTargetFPS Profiler标记占用。应用程序已将Application.targetFrameRate设置为30 fps,并且VSync已启用。主线程上的实际处理工作在大约19毫秒时完成,其余时间用于等待33.33毫秒的剩余时间过去,然后再开始下一帧。尽管这个时间用Profiler标记表示,但主CPU线程在此期间基本上处于空闲状态,允许CPU冷却并使用最少的电池电量。

在其他平台上或如果禁用VSync,需注意的标记可能会有所不同。重要的是检查主线程是否在您的帧预算内运行,或者是否正好在您的帧预算上,并有某种标记指示应用程序正在等待VSync,以及其他线程是否有任何空闲时间。

空闲时间由灰色或黄色Profiler标记表示。上面的截图显示渲染线程在Gfx.WaitForGfxCommandsFromMainThread中处于空闲状态,这表示它在一帧中完成了向GPU发送绘制调用,并在等待CPU在下一帧中发出更多绘制调用请求。同样,尽管Job Worker 0线程在Canvas.GeometryJob中花费了一些时间,但大部分时间它是空闲的。这些都是应用程序在帧预算内舒适运行的迹象。

如果您的游戏在帧预算内

如果您在帧预算内,包括为电池使用和热限制所做的任何预算调整,您就完成了关键的分析任务。您可以通过运行Memory Profiler来得出结论,以确保应用程序也在其内存预算内。

上面的图像显示一款游戏在30 fps所需的~22毫秒帧预算内舒适运行。注意WaitForTargetfps填充主线程时间直到VSync,以及渲染线程和工作线程中的灰色空闲时间。还要注意,通过查看Gfx.Present帧的结束时间,可以观察到VBlank间隔,并且您可以在时间线区域或顶部的时间标尺上绘制时间尺度,以测量从一个到下一个。

CPU绑定

CPU受限

如果您的游戏不在CPU帧预算内,下一步是调查CPU的哪个部分是瓶颈——换句话说,哪个线程最忙。

整个CPU工作负载成为瓶颈的情况很少见。现代CPU有多个不同的核心,能够独立且同时执行工作。不同的线程可以在每个CPU核心上运行。一个完整的Unity应用程序使用一系列线程来实现不同的目的,但最常见的用于查找性能问题的线程是:

  • 主线程:这是大多数游戏逻辑/脚本默认执行工作的地方。大多数Unity系统,如物理、动画、UI和渲染的初始阶段,都在这里执行。
  • 渲染线程:这处理准备工作(例如,场景中哪些对象对相机可见,哪些因超出视锥体、被遮挡或因其他标准被剔除/不可见)必须在将渲染指令发送到GPU之前完成。
  • 在渲染过程中,主线程检查场景并执行相机剔除、深度排序和绘制调用批处理,生成要渲染的内容列表。该列表传递给渲染线程,渲染线程将其从Unity的内部平台无关表示转换为特定图形API调用,以指示特定平台上的GPU。
  • 作业工作线程:开发人员可以利用作业系统来调度某些类型的工作在工作线程上运行,从而减少主线程的工作负载。Unity的一些系统和功能也利用作业系统,例如物理、动画和渲染。

主线程优化的真实世界示例

下图显示了在主线程受限的项目中可能的情况。该项目在Meta Quest 2上运行,通常目标帧预算为13.88毫秒(72 fps)或甚至8.33毫秒(120 fps),因为高帧率对于避免VR设备中的运动病很重要。然而,即使这个游戏的目标是30 fps,很明显这个项目有问题。

尽管渲染线程和工作线程看起来与在帧预算内的示例相似,但主线程在整个帧期间显然忙于工作。即使考虑到帧结束时小量的分析器开销,主线程仍然忙于超过45毫秒,这意味着该项目的帧率低于22 fps。没有标记显示主线程在等待VSync;它在整个帧期间都很忙。

下一阶段的调查是识别帧中耗时最长的部分,并理解原因。在这个帧中,PostLateUpdate.FinishFrameRendering 花费了 16.23 毫秒,超过了整个帧预算。更仔细的检查显示,有五个名为 Inl_RenderCameraStack 的标记实例,表明有五个相机处于活动状态并渲染场景。由于 Unity 中的每个相机都会调用整个渲染管线,包括剔除、排序和批处理,因此该项目的最高优先级任务是 减少活动相机的数量,理想情况下只保留一个。

BehaviourUpdate,包含所有 MonoBehaviour.Update() 方法执行的 Profiler 标记,在这个帧中花费了 7.27 毫秒。

在时间线视图中,品红色部分表示脚本分配托管堆内存的点。切换到 Hierarchy 视图,并在搜索栏中输入 GC.Alloc 进行过滤,显示分配此内存在此帧中大约需要 0.33 毫秒。然而,这并不是内存分配对 CPU 性能影响的准确测量。

GC.Alloc 标记并不是通过记录开始和结束点来计时的,像典型的 Profiler 样本那样。为了最小化它们的开销,Unity 仅记录分配的时间戳和分配的大小。

Profiler 为 GC.Alloc 标记分配了一个小的人工样本持续时间,仅仅是为了确保它们在 Profiler 视图中可见。实际的分配可能需要更长时间,特别是如果需要从系统请求新的内存范围。为了更清楚地看到影响,请在执行分配的代码周围放置 Profiler 标记;在深度分析中,时间线视图中品红色 GC.Alloc 样本之间的间隙提供了一些指示,说明它们可能花费了多长时间。

此外,分配新内存可能对性能产生负面影响,这些影响更难以测量并直接归因于它们:

  • 从系统请求新内存可能会影响移动设备的功率预算,这可能导致系统减慢 CPU 或 GPU 的速度。
  • 新内存可能需要加载到 CPU 的 L1 缓存中,从而推送现有的缓存行。
  • 增量或同步垃圾收集可能会直接或延迟触发,因为托管内存中现有的空闲空间最终会被超出。

在帧开始时,四个 Physics.FixedUpdate 实例总共花费了 4.57 毫秒。稍后,LateBehaviourUpdate(对 MonoBehaviour.LateUpdate() 的调用)花费了 4 毫秒,而 Animators 大约占 1 毫秒。为了确保该项目达到预期的帧预算和速率,所有这些主线程问题都需要调查以找到合适的优化方案。

主线程瓶颈的常见陷阱

通过优化花费最长时间的事情,将获得最大的性能提升。以下领域通常是寻找优化项目的主要线程绑定的丰硕之地:

  • 物理计算
  • MonoBehaviour 脚本更新
  • 垃圾分配和/或收集
  • 在主线程上进行相机剔除和渲染
  • 低效的绘制调用批处理
  • UI 更新、布局和重建
  • 动画

阅读我们的优化指南,提供了一长串可操作的优化常见陷阱的提示:

根据您想要调查的问题,其他工具也可能有帮助:

  • 对于耗时较长但未能准确显示原因的 MonoBehaviour 脚本,请在代码中添加 Profiler Markers 或尝试 深度分析 以查看完整的调用堆栈。
  • 对于分配托管内存的脚本,请启用 Allocation Call Stacks 以准确查看分配来源。或者,启用深度分析或使用 Project Auditor,该工具显示按内存过滤的代码问题,以便您可以识别所有导致托管分配的代码行。
  • 使用 Frame Debugger 调查绘制调用批处理不良的原因。

有关优化游戏的全面提示,请免费下载这些 Unity 专家指南:

CPU受限:渲染线程

CPU受限:渲染线程

这是一个受其渲染线程限制的实际项目。这是一个具有等距视角的控制台游戏,目标帧预算为 33.33 毫秒。

Profiler 捕获显示,在当前帧的渲染开始之前,主线程等待渲染线程,如 Gfx.WaitForPresentOnGfxThread 标记所示。渲染线程仍在提交来自上一帧的绘制调用命令,并且尚未准备好接受来自主线程的新绘制调用;它还在 Camera.Render 中花费时间。

您可以区分与当前帧相关的标记和来自其他帧的标记,因为后者看起来更暗。您还可以看到,一旦主线程能够继续并开始发出绘制调用供渲染线程处理,渲染线程需要超过100毫秒来处理当前帧,这在下一帧期间也会造成瓶颈。

进一步调查显示,这款游戏有一个复杂的渲染设置,涉及九个不同的摄像机和许多由于替换着色器引起的额外通道。该游戏还使用前向渲染路径渲染超过130个点光源,这可能为每个光源增加多个额外的透明绘制调用。总的来说,这些问题结合在一起导致每帧超过3000个绘制调用。

渲染线程瓶颈的常见陷阱

以下是需要调查的常见原因,适用于渲染线程受限的项目:

  • 绘制调用批处理不佳:这特别适用于较旧的图形API,如OpenGL或DirectX 11。
  • 摄像机过多:除非您正在制作分屏多人游戏,否则您应该只拥有一个活动摄像机。
  • 剔除不佳:这导致绘制的内容过多。检查您的摄像机的视锥体尺寸和剔除层掩码。

渲染分析器模块显示每帧的绘制调用批次数和SetPass调用的概述。调查您的渲染线程向GPU发出的绘制调用批次的最佳工具是帧调试器。

解决识别出的瓶颈的工具

虽然本电子书的重点是识别性能问题,但我们之前强调的两个互补性能优化指南提供了关于如何解决瓶颈的建议,具体取决于您的目标平台是PC或控制台还是移动设备。在渲染线程瓶颈的背景下,值得强调的是,Unity根据您识别出的问题提供不同的批处理系统和选项。以下是一些选项的快速概述,我们将在电子书中更详细地解释:

  • SRP批处理通过在GPU内存中持久存储材料数据来减少CPU开销。虽然它并没有减少实际的绘制调用数量,但它使每个绘制调用变得更便宜。
  • GPU实例化将使用相同材料的相同网格的多个实例组合成一个绘制调用。
  • 静态批处理将共享相同材料的静态(非移动)网格组合在一起,因此在处理具有许多静态元素的关卡设计时可以获得优势。
  • GPU驻留绘制器自动使用GPU实例化来减少CPU开销和绘制调用,通过将相似的GameObjects分组在一起。
  • 动态批处理在运行时组合小网格,这在高绘制调用成本的旧移动设备上可能是一个优势。然而,缺点是顶点变换也可能是资源密集型的。
  • GPU遮挡剔除使用计算着色器通过比较当前帧和前一帧的深度缓冲区来确定对象的可见性,从而减少不必要的遮挡对象渲染,而无需预先烘焙数据。

此外,在CPU端,可以使用Camera.layerCullDistances等技术通过根据对象与相机的距离剔除对象来减少发送到渲染线程的对象数量,从而帮助缓解相机剔除期间的CPU瓶颈。

这些只是可用选项中的一些。这些每一个都有不同的优点和缺点。有些仅限于某些平台。项目通常需要结合使用这些系统中的几种,为此,需要了解如何充分利用它们。

CPU受限:工作线程

CPU受限:工作线程

被主线程或渲染线程以外的CPU线程绑定的项目并不常见。然而,如果您的项目使用面向数据的技术栈(DOTS),尤其是如果工作被移出主线程到使用作业系统的工作线程中,则可能会出现这种情况。

上面的图像是编辑器中播放模式的捕获,显示了在CPU上运行粒子流体模拟的DOTS项目。

乍一看,这似乎是一个成功。工作线程紧密打包着Burst编译的作业,表明大量工作已被移出主线程。通常,这是一个明智的决定。

然而,在这种情况下,主线程的帧时间为48.14毫秒,灰色的WaitForJobGroupID标记为35.57毫秒,表明一切并不顺利。WaitForJobGroupID 表示主线程已调度作业在工作线程上异步运行,但在工作线程完成运行之前,它需要这些作业的结果。在 WaitForJobGroupID 下面的蓝色 Profiler 标记显示主线程在等待时运行作业,以确保作业尽快完成。

尽管作业是 Burst 编译的,但它们仍然在做很多工作。也许这个项目使用的空间查询结构应该被优化或替换为更高效的结构,以快速找到彼此接近的粒子。或者,空间查询作业可以安排在帧的末尾,而不是开始时,结果在下一帧开始时不需要。也许这个项目试图模拟太多粒子。需要进一步分析作业的代码以找到解决方案,因此添加更细粒度的 Profiler 标记可以帮助识别它们最慢的部分。

您项目中的作业可能没有像这个例子中那样并行化。也许您只是有一个长作业在单个工作线程中运行。这没问题,只要作业被调度的时间和需要完成的时间之间的间隔足够长,以便作业可以运行。如果不是,您会看到主线程停滞,因为它在等待作业完成,如上面的截图所示。

工作线程瓶颈的常见陷阱

同步点和工作线程瓶颈的常见原因包括:

  • 作业未被 Burst 编译器编译
  • 在单个工作线程上运行的长作业,而不是在多个工作线程之间并行化
  • 在调度作业的帧中的时间点和结果所需的时间点之间的时间不足
  • 帧中的多个“同步点”,要求所有作业立即完成

您可以使用 Flow Events 功能在 CPU 使用情况 Profiler 模块的时间线视图中调查作业何时被调度以及主线程何时期望它们的结果。

有关编写高效 DOTS 代码的更多信息,请参阅 DOTS 最佳实践 指南。

GPU绑定

GPU绑定

如果主线程在 Profiler 标记中花费大量时间,如 Gfx.WaitForPresentOnGfxThread,并且您的渲染线程同时显示标记,如 Gfx.PresentFrame.WaitForLastPresent.

获取GPU帧时间的最佳方法是使用特定于目标平台的GPU分析工具,但并非所有设备都能轻松捕获可靠数据。

FrameTimingManager API在这些情况下可能会很有帮助,提供低开销、高级别的CPU和GPU帧时间。

上述捕获是在使用Vulkan图形API的Android手机上进行的。尽管在此示例中,Gfx.PresentFrame中花费的一些时间可能与等待VSync有关,但此Profiler标记的极长时间表明大部分时间花费在等待GPU完成渲染前一帧。

在这个游戏中,某些游戏事件触发了一个着色器的使用,该着色器使GPU渲染的绘制调用数量增加了三倍。在分析GPU性能时需要调查的常见问题包括:

  • 昂贵的全屏后处理效果,如环境光遮蔽和辉光
  • 由于以下原因导致的昂贵片段着色器:
  • 着色器代码中的分支逻辑
  • 使用全浮点精度而不是半精度,尤其是在移动设备上
  • 寄存器的过度使用,影响GPU的波前占用率
  • 透明渲染队列中的过度绘制,由以下原因造成:
  • 低效的UI渲染
  • 重叠或过度使用粒子系统
  • 后处理效果
  • 过高的屏幕分辨率,例如:
  • 4K显示器
  • 移动设备上的Retina显示器
  • 由于以下原因导致的微三角形:
  • 密集的网格几何
  • 缺乏细节层次(LOD)系统,这在移动GPU上是一个特别的问题,但也可能影响PC和控制台GPU。
  • 由于以下原因导致的缓存未命中和浪费的GPU内存带宽:
  • 未压缩的纹理
  • 没有mipmap的高分辨率纹理
  • 几何体或细分着色器,如果启用了动态阴影,可能会在每帧运行多次

如果您的应用程序似乎受限于GPU,您可以使用帧调试器作为快速了解发送到GPU的绘制调用批次的方法。然而,这个工具无法提供任何具体的GPU时间信息,只能展示整体场景是如何构建的。

调查GPU瓶颈原因的最佳方法是检查来自合适GPU分析器的GPU捕获。您使用的工具取决于目标硬件和选择的图形API。有关更多信息,请参见电子书中的分析和调试工具部分

Unity最佳实践指南
更多针对Unity开发者和创作者的提示

Unity最佳实践中心获取更多最佳实践和提示。从超过30个指南中选择,这些指南由行业专家、Unity工程师和技术艺术家创建,将帮助您高效地使用Unity的工具集和系统进行开发。

游戏性能分析的最佳实践 | Unity