[Profiling] 剖析 Unity 游戏的终极指南[4] - Unity分析和调试工具(下)

英文原文:https://resources.unity.com/games/ultimate-guide-to-profiling-unity-games?utm_source=demand-gen&utm_medium=pdf&utm_campaign=render-with-quality-and-flexibility&utm_content=introduction-to-urp-ebook

内存分析器

  Memory Profiler 是 Unity 包管理器中的一个附加包。 使用 Memory Profiler 对内存进行快照,无论是在编辑器中还是在Player中运行。

  快照显示引擎中的内存分配,使您可以快速识别过多或不必要的内存使用的原因,跟踪内存泄漏,或查看堆碎片.

  安装 Memory Profiler 软件包后,单击 Window > Analysis > Memory Profiler 打开它。

  Memory Profiler 的顶部菜单栏允许您更改播放器选择目标并捕获或导入快照。

在这里插入图片描述
更改播放器选择并捕获或导入内存快照

注意:通过使用 Target selection 下拉菜单将 Memory Profiler 连接到远程设备来分析目标硬件上的内存。 由于编辑器和其他工具增加的开销,Unity 编辑器中的分析会给您提供不准确的数据。

  Memory Profiler 窗口的左侧是 Workbench 区域。 使用它来管理和打开或关闭已保存的内存快照。 您还可以使用此区域在单个和比较快照视图之间切换。

在这里插入图片描述
Workbench 窗格用于管理内存快照。

  与 Profile Analyzer 类似,Memory Profiler 允许您加载两个数据集(内存快照)以进行比较。 这在查看内存使用量如何随时间或场景之间增长以及搜索内存泄漏时特别有用。

  Memory Profiler 在主窗口中有许多选项卡,可让您深入了解内存快照,包括摘要、对象和分配以及碎片。 让我们详细看看这些选项中的每一个。

Summary 视图

  如果您想快速了解项目的内存使用情况,请选择此视图。 它还包含有关捕获的内存快照的有用且重要的内存相关数据。 它非常适合快速浏览拍摄快照时发生的情况。

在这里插入图片描述
“Summary” 视图显示捕获快照时的内存概览。

  “摘要”视图还将内存使用情况的细分显示为图形树图,您可以深入了解这些区域以发现消耗最多内存的区域。
在这里插入图片描述
摘要视图还显示捕获快照时内存使用情况的树形图。

  树形图视图下方是一个过滤表,它会更新以显示选定网格单元格中的对象列表。

  树形图显示归属于对象的内存,无论是原生的还是托管的。 Managed Object 内存往往与 Native Object 内存相比相形见绌,因此更难在地图视图中发现。 您可以放大树状图来查看这些内容,但对于检查较小的对象,表格通常可以提供更好的概览。 单击树形图中的单元格会将其下方的表格过滤为部分的类型和/或在“对象和分配”视图中选择感兴趣的特定对象。

在这里插入图片描述
内存快照中的对象过滤表更新以显示当前选定的树图网格单元格中的对象

  您可以通过选择表行或代表它的树形图网格单元格,然后检查详细信息侧面板中的引用部分,来跟踪此列表中哪些项目引用对象以及这些引用驻留在哪些托管类字段中。 如果一侧是隐藏的,您可以通过工具栏窗口右上角的切换按钮使其可见。

在这里插入图片描述
详细信息面板,包含参考和选择详细信息部分。 参考部分显示对在树形图或表中选择的对象的参考。 选择详细信息部分包含有关该对象或在参考部分中选择的任何对象的详细信息。

注意:树图只显示内存中的对象。 它不是跟踪内存的完整表示。 如果您注意到内存使用概览数字与跟踪的内存总数不同,了解这一点很重要。

  这是因为并非所有本机内存都与对象相关联。 它还可以由非对象关联的本地分配组成,例如可执行文件和 DLL、NativeArrays 等。 甚至更抽象的概念,例如“保留但未使用的内存空间”可以在 Native Allocations 总数中发挥作用。

对象和分配

  Objects and Allocations 视图显示了一个表格,可以根据现成的选择(例如所有对象、所有本机对象、所有托管对象、所有本机分配等)切换到过滤器。

在这里插入图片描述
Objects and Allocations 表可以在多个级别进行过滤,允许您以高粒度深入到捕获的快照内存使用情况。Objects and Allocations 表可以在多个级别进行过滤,允许您以高粒度深入到捕获的快照内存使用情况 粒度。

  您可以切换底部表格以显示所选范围内的对象、分配或内存区域。

  在优化内存使用并针对内存预算有限的硬件平台更有效地打包内存时,请利用此优势。

内存分析技术和工作流程

  加载 Memory Profiler 快照并通过 Tree Map 视图检查类别,按内存占用大小从大到小排序。

  项目资产通常是内存的最高消费者。 使用表格视图,定位纹理对象、网格、音频剪辑、渲染纹理、着色器变体和预分配缓冲区。 这些都是内存优化的好候选。

定位内存泄漏

内存泄漏通常发生在以下情况:

  • 对象不是通过代码手动从内存中释放的
  • 一个对象由于无意的引用而留在内存中

  Memory Profiler Diff 视图可以通过比较特定时间范围内的两个快照来帮助查找内存泄漏。

  Unity 游戏中常见的内存泄漏场景可能会在卸载场景后发生。

  Memory Profiler 软件包有一个工作流程,可指导您完成使用 Diff 视图发现这些类型的泄漏的过程

在应用程序生命周期内定位重复的内存分配

  通过多个内存快照的差异比较,您可以识别应用程序生命周期内连续内存分配的来源。

内存分析器模块
Unity Profiler 中的 Memory Profiler 模块用红线表示每帧的托管分配。 大多数情况下这应该为 0,因此该行中的任何峰值都表示您应该调查托管分配的帧。Unity Profiler 中的内存分析器模块用红线表示每帧的托管分配。 大多数情况下这应该是 0,因此该行中的任何尖峰都表示您应该调查托管分配的帧。
在这里插入图片描述
GC Allocated In Frame 出现的任何峰值都会为您提供调查托管分配的指针。

CPU Usage Profiler 模块中的时间线视图
CPU Usage Profiler 模块中的 Timeline 视图以粉红色显示分配,包括托管的分配,使它们易于查看和调整。
在这里插入图片描述
托管分配在时间轴视图中显示为粉色标记。

分配调用堆栈
分配调用堆栈提供了一种在代码中发现托管内存分配的快速方法。 与深度分析通常添加的内容相比,这些将提供您需要的调用堆栈详细信息,并且可以使用标准 Profiler 即时启用它们。

默认情况下,Profiler 中禁用分配调用堆栈。 要启用它们,请单击 Profiler 窗口主工具栏中的 Call Stacks 按钮。 将详细信息视图更改为相关数据。

注意:如果您使用的是旧版本的 Unity(在分配调用堆栈支持之前),那么深度分析是获取完整调用堆栈以帮助查找托管分配的好方法。
在这里插入图片描述
在 Profiler 中启用分配调用堆栈将允许您按照调用堆栈返回到托管分配的源。
在这里插入图片描述
Hierarchy 视图中的 Related Data 面板还将显示分配调用堆栈的详细信息

在 Hierarchy 或 Raw Hierarchy 中选择的 GC.Alloc 样本现在将包含它们的调用堆栈。 您还可以在 Timeline 的选择工具提示中看到 GC.Alloc 示例的调用堆栈。

CPU Usage Profiler 模块中的 Hierarchy 视图
CPU Usage Profiler 模块中的 Hierarchy 视图允许您单击列标题以将它们用作排序标准。 按 GC Alloc 排序是专注于这些方面的好方法。
在这里插入图片描述
使用 CPU Usage Profiler 模块中的 Hierarchy 视图是过滤和关注托管分配的好方法

Project Auditor
Project Auditor 是一个实验性的静态分析工具。 它做了很多有用的事情,其中一些超出了本指南的范围,但它可以生成项目中导致托管分配的每一行代码的列表,而无需运行项目。 这是查找和调查此类问题的一种非常有效的方法。

内存和 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 对象,而不是在让步行中创建它。
    LINQ 和正则表达式:这两者都从幕后的装箱中生成垃圾。 如果性能是一个问题,请避免使用 LINQ 和正则表达式。 编写 for 循环并使用列表作为创建新数组的替代方法
    通用集合和其他托管类型:不要在更新中的每一帧都声明和填充列表或集合(例如,玩家一定半径内的敌人列表)。 相反,使 List 成为 MonoBehaviour 的成员并在 Start 中对其进行初始化。 只需在使用前使用 Clear every frame 清空集合

    尽可能定时垃圾收集
    如果您确定垃圾回收冻结不会影响游戏中的特定点,您可以使用 System.GC.Collect 触发垃圾回收。

    请参阅了解自动内存管理以获取有关如何利用此优势的示例。

    使用增量垃圾收集器拆分 GC 工作负载
    增量垃圾收集不是在程序执行期间创建一个单一的长时间中断,而是使用多个较短的中断,将工作负载分布在许多帧上。 如果垃圾收集导致帧率不规则,请尝试此选项,看看它是否可以减少 GC 尖峰问题。 使用 Profile Analyzer 来验证它对您的应用程序的好处。

    请注意,在增量模式下使用 GC 会为某些 C# 调用添加读写障碍,这会带来一些开销,每帧脚本调用开销最多可增加约 1 毫秒。 为了获得最佳性能,最好在主游戏循环中不使用 GC Alloc,这样您就不需要增量 GC 来获得平滑的帧速率,并且可以将 GC.Collect 隐藏在用户不会注意到的地方,例如当 打开菜单或加载新关卡。

要了解有关 Memory Profiler 的更多信息,请查看以下资源:

Deep profiling

  如 Profiling 101 部分所述,默认情况下 Unity 仅对显式包装在 Profiler 标记中的代码进行分析。 这包括由引擎的本机代码调用的托管代码的第一个调用堆栈深度。

  启用深度分析将导致在每个函数调用的开头和结尾插入 Profiler 标记。 这允许捕获大量细节。 使用 Deep Profile 设置来计算在没有显示足够的调用堆栈的长 Profiler 标记中发生了什么。

  这种测量游戏性能的细粒度方法可能比基于快照的分析(样本分析)更可取,后者有可能错过捕获中的细节。

请务必查看 ProfileMarker API,作为手动检测有问题的代码区域的一种方式。

与Deep profiling相比,它们对性能的影响要低得多。 有时,添加 ProfileMarker 并重新构建游戏比在启用 Deep Profiling 的情况下进入要测试的游戏部分更快。

在设备构建上获得完整调用堆栈的另一种方法是运行本机 CPU 分析器。 在某些情况下,这比深度剖析更容易且对性能的干扰更少。

何时使用Deep profiling

  只有在您确定了需要更详细检查的应用程序或托管代码的特定部分后,您才应该启用 Deep Profile 设置。 深度分析是资源密集型的,并且会消耗大量内存。 启用后,您的应用程序运行速度会变慢。

  深度分析允许您详细遍历调用树并发现代码中的低效率或问题。

在这里插入图片描述
当您需要跟踪和了解特定问题时,Deep profiling会打开大量细节。

注意:从 Unity 2019.3 开始添加了在 Mono 和 IL2CPP 后端中对深度分析的支持,这对于必须使用 IL2CPP 的平台(例如 iOS)来说是个好消息。

使用 Deep profiling

  要在播放器构建中使用深度分析,您需要通过 File > Build Settings > Deep Profiling Support 启用它。

在这里插入图片描述
启用支持后,您可以随时在 Profiler 窗口中轻松打开或关闭构建的 Deep Profiling。

  附加到播放器时淡出的 Deep Profile 按钮表示没有为您的构建启用 Deep Profiling 支持。

在这里插入图片描述
Deep Profile揭示了有关应用程序代码性能和时序的更多信息。 它显示了完整的方法调用树,帮助您深入了解托管分配发生的位置

Deep Profile 技巧

自上而下的方法

  在分析您的应用程序时,请从较高级别开始,并尝试找到可以在不使用深度分析的情况下提高性能的区域。 当您需要更多信息时,您可以在 Profiler 中启用 Deep Profiling 以进行更细粒度的挖掘。 使用这种方法将有助于将 Profiler Hierarchy 中显示的信息级别保持在最低水平,从而使您能够专注于手头的目标。
仅在绝对必要时进行Deep Profile
通常,当您需要获取有关代码性能的低级详细信息时,最好使用深度分析。 虽然为构建启用 Deep Profiling 标志不会影响性能而不实际切换功能启用,但启用时,它会导致您的应用程序运行缓慢。

如果您只对在代码中查找托管分配的来源感兴趣,请记住 Unity 2019.3 及更高版本允许您在无需启用深度分析的情况下执行此操作。 使用 Profiler 中的调用堆栈切换和调用下拉菜单来帮助定位托管分配。

自动化流程中的Deep Profile
要在从命令行进行分析时打开 Deep Profiling,请将 -deepprofiling 参数添加到您的构建可执行文件中。 对于 Android / Mono 脚本后端构建,使用如下 adb 命令行参数: adb shell am start -n com.company.game/com.unity3d.player.UnityPlayerActivity -e ‘unity’ ‘-deepprofiling’

对低规格硬件进行Deep Profile
低规格硬件的内存和性能有限,可能会影响您使用深度分析的能力。 Unity 的 Profiler 样本存储在环形缓冲区中,当在较慢的设备上使用 Deep Profile 设置时,该缓冲区可能会被填满。 如果发生这种情况,Unity 将显示一条错误消息。

您可以通过设置 Profiler.maxUsedMemory 属性(字节)为 Profiler 分配更多内存用于缓冲数据。 播放器的默认值为 128 MB,Editor 的默认值为 512 MB。 如果您在深度分析时遇到问题,请根据需要在较慢的设备 Player 构建上增加此值。

如果由于深度分析会增加开销,您需要在运行速度过慢(或根本不运行)的硬件上更详细地分析代码,您可以使用自己的标记进行更深入的分析。

无需启用 Deep Profile 设置,而是将 Profiler 标记添加到代码中感兴趣的特定区域。 查看 CPU 使用率模块时,这些标记将出现在 Profiler Timeline 或 Hierarchy 中。

在这里插入图片描述
当 Deep Profile 增加太多开销时,添加 Profiler 标记以分析更深层的代码。


使用哪些分析工具以及何时使用?

  在项目生命周期开始时开始分析提供了最大的好处。 通过尽早开始,您可以建立有助于在游戏和应用程序开发进一步检查点进行比较的基线。 重要的是要知道从“分析工具带”中选择哪种工具以及何时选择。

  一旦您了解了每种工具的用途和好处,您就会更容易知道何时使用它们。 请务必在 Unity profiling 和调试工具部分了解 Unity 必须提供的每个分析工具。

  为了帮助回答“何时”,这里列出了项目生命周期中的检查点想法,在规划分析策略时可能有用参考。

  • 原型:分析对于降低项目原型阶段的风险很重要。 如果游戏设计文档要求屏幕上有 10,000 个敌人,您需要能够构建和配置原型,证明这样的事情在目标平台上是可能的。 如果不是,则需要更改设计。

  • 项目的早期阶段:为选定的目标设备硬件的项目性能建立基线。 使用 Memory Profiler 大致了解内存使用情况,并确保项目范围的计划不会趋向于目标硬件的内存限制将成为进一步问题的程度。

  • 冲刺结束:如果您在敏捷团队的冲刺中工作,那么冲刺结束候选版本 (RC) 是运行标准化分析工具套件的好时机。 确保您有标准格式来记录结果和指标,例如在数据库或电子表格中。 使用 Unity Profiler 执行以下分析活动和数据捕获:
    — CPU Usage
    — GPU Usage
    — Memory Usage
    — Rendering
    — Physics

更深入地使用这些工具来记录结果和关键差异指标(与之前的 sprint 版本的差异):

  • Profile Analyzer:加载以前版本的分析数据捕获并比较和记录差异。
  • Memory Profiler:比较之前的候选版本构建内存快照并记录内存增加或减少的差异。
自动化关键性能和分析指标

在这里插入图片描述
Grafana 仪表板中捕获和可视化的自动每周构建分析数据

  通过自动执行常见和重复性任务来提升您的项目分析和数据捕获。 这将节省您的时间,并且您将受益于始终保持最新的指标。

  可以将指标绘制成图表并将其添加到项目仪表板中,从而使团队可以查看性能在哪里急剧下降(例如,新添加的功能或错误),或者在优化和错误修复冲刺之后情况有所改善。

  绘制项目在游戏各个级别随时间推移的总体内存使用情况。 通过使用 Memory Profiler 捕获内存快照并对所有级别的数据进行平均,您可以记录每个目标设备/平台、sprint 或发布周期的内存占用量。

在这里插入图片描述
使用 Cloud Build 等 Unity DevOps 工具来自动化分析工作流。

  如果您希望记录高级分析统计信息,请使用 ProfilerRecorder 记录诸如总保留内存或系统已用内存之类的指标,并将其输出到 CI(持续集成),将它们定向到图表或绘图工具(例如 Grafana)。

  使用 Cloud Build 等 Unity DevOps 工具自动创建发布版本,并将此过程与自动化设备分析工作流集成。

自动分析管道示例

  自动化可以帮助确保您的团队实现分析构建的好处,而不必担心此过程会由于时间限制而被取消优先级。

  此示例工作流展示了如何使用自动化来确保构建分析频繁且准确地进行。

  • 使用 Unity Cloud Build 创建自动构建版本。
  • 每次发布后,使用脚本启动构建的播放器并捕获超过 2000 帧的原始分析数据:
    — AngryBots2.exe -profiler-enable -profiler-log-file profile1.raw -profiler-capture-frame-count 2000。要了解有关命令行参数的更多信息,请查看 Unity 文档。

注意:这里的另一个选项是使用脚本每 300 到 2000 帧切换到一个新的日志文件(例如 profile_<N+1>.raw),或者在应用程序的测试周期中分析关键点(检查点在 自动关卡播放)。 如果稍后在仪表板图表中发现问题区域,则可以引用此存储的数据。

在这里插入图片描述
ProfileReader 的编辑器界面

  • 分析数据在 profile1.raw 文件中捕获,现在可以解析为关注的指标。
  • 下一步使用 ProfileReader(一种用于解析原始配置文件数据并将其转换为 CSV 格式的工具)将原始配置文件数据转换为更易读的 CSV 格式。
  • ProfileReader 可以在命令行上使用,因此 Pipeline 的这个阶段将是一个脚本来执行它:
    • Unity.exe -batchMode -projectPath “AngryBots2” -logFile .\Editor. log -executeMethod UTJ.ProfilerReader.CUIInterface.ProfilerToCsv -PH.inputFile “profile1.raw” -PH.timeout 2400 -PH.log

在这里插入图片描述
在 Grafana 仪表板中可视化自动分析数据。 在此屏幕截图中,看起来有人让物理对象创建错误潜入构建。

  • 通过从 CSV 解析数据,自动化管道将您的每晚、每周或 sprint 版本的数据上传到 Grafana 等工具以进行可视化。

随着数据的可视化和自动更新,您的团队可以轻松发现图表何时出现峰值,从而更快地识别问题。 他们还可以查看性能优化任务的结果或关卡设计团队在游戏中跨各个关卡进行内存优化传递的结果。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值