
Unity 中的内存性能分析
内存性能分析
内存性能分析对于测试硬件平台内存限制、减少加载时间和崩溃,以及使您的项目与旧设备兼容非常有用。如果您想通过进行实际增加内存使用的更改来提高 CPU/GPU 性能,它也可能相关。它与运行时性能基本无关。
在 Unity 中,有两种分析应用程序内存使用情况的方法。
内存性能分析器模块:这是一个内置的性能分析器模块,可以为您提供有关应用程序使用内存的基本信息。
内存性能分析器包:这是一个您可以添加到项目中的 Unity 包。它为 Unity 编辑器添加了一个额外的内存性能分析器窗口,您可以使用它更详细地分析应用程序中的内存使用情况。可以存储和比较快照以便查出内存泄漏,或者查看内存布局以查出内存碎片问题。
通过这些内置工具,您可以监控内存使用情况,定位应用程序中内存使用高于预期的区域,并查找和改善内存碎片。

理解并定义内存预算
理解和预算目标设备的内存限制对于多平台开发至关重要。在设计场景和关卡时,请遵循为每个目标设备设置的内存预算。通过设置限制和指导方针,您可以确保应用程序在每个平台的硬件规格范围内良好运行。
您可以在开发者文档中找到设备内存规格。例如,Xbox One 控制台在前台运行的游戏最大可用内存限制为 5 GB,根据文档。
围绕网格和着色器复杂性以及纹理压缩设置内容预算也很有用。这些都会影响分配的内存量。在项目开发周期中可以参考这些预算数字。
确定物理 RAM 限制
每个目标平台都有内存限制,一旦您知道它,就可以为应用程序设置内存预算。使用内存性能分析器查看捕获快照。硬件资源(见上图)显示了物理随机存取内存(RAM)和视频随机存取内存(VRAM)的大小。这个数字没有考虑到并非所有的空间都可能可用。然而,它提供了一个有用的粗略数字来开始工作。
交叉参考目标平台的硬件规格是个好主意,因为这里显示的数字可能并不总是完整的。开发者工具包硬件有时会有更多的内存,或者您可能正在使用具有统一内存架构的硬件。
确定最低 RAM 规格
确定您支持的每个平台中 RAM 规格最低的硬件,并以此指导您的内存预算决策。请记住,并非所有的物理内存都可能可用。例如,控制台可能会运行一个虚拟机来支持旧游戏,这可能会使用部分总内存。考虑使用一个百分比(例如,总数的 80%)。对于移动平台,您可能还考虑将规格分为多个层次,以支持更高端设备的更好质量和功能。
考虑大型团队的每个团队预算
一旦您定义了内存预算,请考虑为每个团队设置内存预算。例如,您的环境艺术家在加载的每个关卡或场景中获得一定量的内存使用,音频团队获得音乐和音效的内存分配,等等。
随着项目的进展,灵活处理预算是很重要的。如果一个团队的预算远低于预期,可以将剩余的分配给另一个团队,以改善他们正在开发的游戏领域。
一旦您为目标平台决定并设置了内存预算,下一步是使用分析工具帮助您监控和跟踪游戏中的内存使用情况。

内存性能分析器模块的两个视图
内存性能分析器模块提供两种视图:简单和详细。使用简单视图获取应用程序内存使用的高层次视图。在必要时,切换到详细视图以进一步深入。
简单
总保留内存数字是“Unity内存跟踪的总数”。它包括Unity已保留但当前未使用的内存(该数字是总使用内存)。
系统使用内存数字是操作系统认为正在被您的应用程序使用的内存。如果该数字显示为0,请注意这表示您正在分析的平台上未实现Profiler计数器。在这种情况下,最可靠的指标是总保留内存。在这些情况下,建议切换到本地平台分析工具以获取详细的内存信息。

内存性能分析器中的详细视图
要查看您的可执行文件、DLL和Mono虚拟机使用了多少内存,逐帧内存数字是不够的。使用详细快照捕获深入了解这种内存细分。
注意:内存分析器模块详细视图中的引用树仅显示本地引用。从继承自UnityEngine.Object的对象的引用可能会显示其托管外壳的名称。然而,它们可能仅仅是因为它们下面有本地对象而显示。您不一定会看到任何托管类型。让我们以一个字段中有Texture2D的对象作为参考。使用此视图,您也不会看到哪个字段持有该引用。对于这种细节,请使用内存分析器包。
要在高层次上确定内存使用何时开始接近平台预算,请使用以下“草稿纸”计算:
系统使用内存(或如果系统使用显示为0则为总保留内存)+ 未跟踪内存的粗略缓冲区 / 平台总内存
当该数字开始接近您平台内存预算的100%时,请使用内存分析器包找出原因。
内存分析器模块的许多功能已被内存分析器包所取代,但您仍然可以使用该模块来补充您的内存分析工作。
例如:
- 要查找GC分配:尽管这些在模块中显示,但使用项目审计器或深度分析更容易追踪。
- 快速查看堆的已用/保留大小
- 着色器内存分析
在设置内存预算时,请记住在具有最低规格的设备上进行分析,以满足您的整体目标平台。密切监控内存使用情况,牢记您的目标限制。
您通常希望使用具有大量可用内存的强大开发者系统进行分析(存储大型内存快照或快速加载和保存这些快照的空间很重要)。
内存分析与CPU和GPU分析是不同的,因为它本身可能会产生额外的内存开销。您可能需要在高端设备(具有更多内存)上分析内存,但特别要注意低端目标规格的内存预算限制。
分析内存使用时需要考虑的要点:
- 质量级别、图形层级和AssetBundle变体等设置在更强大的设备上可能具有不同的内存使用情况。例如:
- 质量级别和图形设置可能会影响用于阴影图的RenderTextures的大小。
- 分辨率缩放可能会影响屏幕缓冲区、RenderTextures和后处理效果的大小。
- 纹理质量设置可能会影响所有纹理的大小。
- 最大LOD可能会影响模型等。
- 如果您有像HD(高清)和SD(标准清晰度)版本的AssetBundle变体,并根据设备规格选择使用哪个版本,您可能还会根据您正在分析的设备获得不同的资产大小。
- 目标设备的屏幕分辨率将影响用于后处理效果的RenderTextures的大小。
- 设备支持的图形API可能会影响着色器的大小,具体取决于API支持哪些变体。
- 拥有一个分层系统,使用不同的质量设置、图形层级设置和Asset Bundle变体,是能够针对更广泛设备的好方法,例如,在4GB移动设备上加载AssetBundle的高清版本,在2GB设备上加载标准清晰度版本。然而,请记住上述内存使用情况的变化,并确保测试两种类型的设备,以及具有不同屏幕分辨率或支持的图形 API 的设备。
注意:Unity 编辑器通常会显示更大的内存占用,因为从编辑器和分析器加载了额外的对象。它甚至可能显示在构建中不会加载到内存中的资产内存,例如来自资产捆绑(取决于地址可寻址模拟模式)或精灵和图集,或在检查器中显示的资产。在编辑器中,一些引用链可能会更加混乱。

内存性能分析器包
内存分析器目前在 Unity 2019 LTS 或更新版本中处于预览状态,但预计将在 Unity 2022 LTS 中得到验证。
内存分析器包的一个巨大好处是,除了捕获本机对象(如内存分析器模块所做的),它还允许您查看托管内存,保存和比较快照,并以更详细的方式探索内存内容,提供内存使用的可视化细分。
快照显示引擎中的内存分配,使您能够快速识别过度或不必要内存使用的原因,追踪内存泄漏,或查看堆碎片。
安装内存分析器包后,通过单击窗口 > 分析 > 内存分析器打开它。
内存分析器的顶部菜单栏允许您更改播放器选择目标并捕获或导入快照。
注意:通过将内存分析器连接到远程设备并使用目标选择下拉菜单在目标硬件上分析内存。在 Unity 编辑器中进行分析会由于编辑器和其他工具添加的开销而给出不准确的数字。

单一和比较快照视图
在内存分析器窗口的左侧是工作台区域。使用此功能管理和打开或关闭已保存的内存快照。您还可以使用此区域在单个和比较快照视图之间切换。
与分析器类似,内存分析器允许您加载两个数据集(内存快照)进行比较。这在查看内存使用随时间或场景之间的增长以及寻找内存泄漏时特别有用。
内存分析器在主窗口中有多个选项卡,允许您深入挖掘内存快照,包括摘要、对象和分配以及碎片。让我们详细看看这些选项。

Summary 视图
当您想快速了解项目的内存使用情况时,请选择此视图。它还包含与所捕获的内存快照相关的有用和重要的内存数据。它非常适合快速查看快照拍摄时发生的情况。

图形树图
树图视图以图形树图的形式显示对象使用的内存细分,您可以深入了解消耗最多内存的对象类型。

树图:过滤表
树图视图下方是一个过滤表,更新以显示所选网格单元中的对象列表。
树图显示分配给对象的内存,可以是本地对象或托管对象。托管对象的内存往往被本地对象的内存所掩盖,使其在图中更难以发现。您可以放大树图以查看这些对象,但对于检查较小的对象,表格通常提供更好的概览。单击树图中的单元格将过滤下方的表格,以显示该部分的类型和/或在表格中选择特定感兴趣的对象。
您可以通过选择表示它的表格行或树图网格单元,追踪此列表中引用对象的项目,以及这些引用所在的托管类字段,然后检查详细信息侧面板中的引用部分。如果侧面板被隐藏,您可以通过工具栏右上角的切换按钮使其可见。
注意:树图仅显示内存中的对象。这不是跟踪内存的完整表示。如果您注意到内存使用概览数字与跟踪内存总数不相同,这一点很重要。
这源于并非所有本地内存都与对象相关。它还可以包括与对象无关的本地分配,例如可执行文件和 DLL、NativeArrays 等。甚至更抽象的概念,如“保留但未使用的内存空间”也可能影响本地分配总数。

对象和分配
对象和分配视图显示一个表,可以切换为基于现成选择进行过滤,例如所有对象、所有本地对象、所有托管对象、所有本地分配等。
您可以切换底部表以显示所选范围内的对象、分配或内存区域。如树图视图所述,并非所有内存都与对象相关,因此所有内存区域和所有本地分配页面可以提供更完整的内存使用情况,其中内存区域还包括保留但当前未使用的内存。
在优化内存使用时利用这一点,并旨在为内存预算有限的硬件平台更有效地打包内存。
内存分析技术和工作流程
加载内存分析器快照并通过树图视图检查类别,按内存占用大小从大到小排序。
项目素材通常是内存的最大消耗者。使用表格视图,定位纹理对象、网格、音频剪辑、渲染纹理、着色器和预分配的缓冲区。这些都是内存优化的良好候选者。
定位内存泄漏
内存泄漏通常发生在:
- 对象未通过代码手动释放内存。
- 一个对象因为无意的引用而保留在内存中
内存分析器比较模式可以通过比较特定时间范围内的两个快照来帮助查找内存泄漏。
在Unity游戏中,一个常见的内存泄漏场景可能发生在卸载场景之后。
内存分析器包有一个工作流程,指导您通过使用比较模式发现这些类型的泄漏。
定位应用程序生命周期内的重复内存分配
通过多个内存快照的差异比较,您可以识别在应用程序生命周期内持续内存分配的来源。
以下部分列出了一些提示,以帮助您识别项目中的托管堆分配。

定位内存分配
Unity Profiler中的内存分析器模块用红线表示每帧的托管分配。这通常应该是0,因此该线中的任何尖峰都表示您应该调查的托管分配的帧。

CPU使用分析器模块中的时间线视图
CPU使用情况分析器模块中的时间线视图以粉红色显示分配,包括托管分配,使其易于查看和聚焦。

分配调用堆栈

CPU使用分析器中的层次视图
CPU 使用分析器中的层次视图允许您单击列标题以将其用作排序标准。按 GC 分配排序是专注于这些内容的好方法。
项目审计器
项目审计员 是一个实验性的静态分析工具。它做了很多有用的事情,其中一些超出了本指南的范围,但它可以生成项目中每一行导致托管分配的代码列表,而无需运行项目。这是一种非常有效的方法来查找和调查这些问题。
内存和GC优化
Unity 使用 Boehm-Demers-Weiser 垃圾收集器,该收集器在完成工作之前停止运行您的程序代码,并在工作完成后恢复正常执行。
注意可能导致 GC 峰值的不必要堆分配。
- 字符串:在 C# 中,字符串是引用类型,而不是值类型。这意味着每个新字符串都将在托管堆上分配,即使它只是暂时使用。减少不必要的字符串创建或操作。避免解析基于字符串的数据文件,如 JSON 和 XML,而是将数据存储在 ScriptableObjects 或 MessagePack 或 Protobuf 等格式中。如果您需要在运行时构建字符串,请使用 StringBuilder 类。
- Unity 函数调用:某些 Unity API 函数会创建堆分配,特别是返回托管对象数组的函数。缓存数组的引用,而不是在循环中间分配它们。此外,利用某些避免生成垃圾的函数。例如,使用 GameObject.CompareTag 而不是手动将字符串与 GameObject.tag 进行比较(因为返回新字符串会产生垃圾)。
- 装箱:避免将值类型变量替换为引用类型变量。这会创建一个临时对象,潜在的垃圾会隐式地将值类型转换为类型对象(例如,int i = 123; object o = i)。相反,尝试提供具体的重写,使用您想要传入的值类型。泛型也可以用于这些重写。
- 协程:尽管 yield 不会产生垃圾,但创建一个新的 WaitForSeconds 对象会。缓存并重用 WaitForSeconds 对象,而不是在 yield 行中创建它,或者使用 yield return null。
- LINQ 和正则表达式:这两者都会从幕后装箱中生成垃圾。如果性能是一个问题,请避免使用 LINQ 和正则表达式。编写 for 循环,并使用列表作为创建新数组的替代方案。
- 泛型集合和其他托管类型:不要在 Update 中每帧声明和填充一个 List 或集合(例如,玩家周围一定半径内的敌人列表)。相反,将 List 作为 MonoBehaviour 的成员,并在 Start 中初始化它。在使用之前,每帧简单地用 Clear 清空集合。
尽可能减少时间垃圾收集
如果您确定垃圾收集的冻结不会影响游戏中的特定点,可以使用 System.GC.Collect 触发垃圾收集。
请参阅 理解自动内存管理 以获取如何利用这一点的示例。
使用增量垃圾收集器来分配 GC 工作负载
增量垃圾收集使用多个较短的中断来分配工作负载,而不是在程序执行期间创建单个长时间的中断。如果垃圾回收导致不规则的帧率,请尝试此选项以查看是否可以减少 GC 峰值的问题。使用性能分析器验证其对您的应用程序的好处。
请注意,在增量模式下使用 GC 会对某些 C# 调用添加读写障碍,这会带来一些开销,可能会增加到每帧约 1 毫秒的脚本调用开销。为了获得最佳性能,理想情况下在主要游戏循环中没有 GC 分配,这样您就不需要增量 GC 来保持平滑的帧率,并且可以在用户不会注意到的地方隐藏 GC.Collect,例如在打开菜单或加载新关卡时。
要了解有关内存性能分析器的更多信息,请查看以下资源:
- 内存性能分析器文档
- 在 Unity 中使用内存性能分析器改善内存使用情况 操作指引
- 内存性能分析器:用于排查与内存相关问题的工具 Unite 会话
- 与内存性能分析器一起工作 Unity Learn 会话
