Version: Unity 6.0 (6000.0)
语言 : 中文
检测所有函数调用
自定义性能分析器模块

分析性能分析器跟踪记录

对应用程序进行性能分析时,可能会遇到一些常见问题。本页概述了如何调查一些常见性能问题的原因。

解析启动跟踪

在查看启动时间的跟踪记录时,需要关注两个关键方法:UnityInitApplicationGraphicsUnityLoadApplication。这两个方法是项目的配置、资源和代码可能影响启动时间的主要原因。

注意:应用程序的启动时间因平台而异。在大多数平台上,启动发生在启动画面出现时。

在 iOS 设备上运行的 Unity 项目示例的 Instruments 跟踪记录
在 iOS 设备上运行的 Unity 项目示例的 Instruments 跟踪记录

在上面的截屏(在 iOS 设备上运行的 Unity 项目示例的 Instruments 跟踪记录)中,在特定于平台的 startUnity 方法中,请注意 UnityInitApplicationGraphicsUnityLoadApplication 方法。

UnityInitApplicationGraphics 执行大量内部工作,例如设置图形设备和初始化 Unity 的大量内部系统。它还通过加载资源系统中包含的所有文件的索引来初始化资源系统

Unity 的资源系统在其数据中包含了项目 Assets 文件夹中 Resources 文件夹中的每个资源文件。其中包括 Resources 文件夹的子文件夹中的所有文件。因此,初始化资源系统所需的时间与应用程序项目的 Resources 文件夹中的文件数量呈正相关关系。

UnityLoadApplication 包含加载并初始化项目的第一个场景的方法。其中包括反序列化并实例化显示第一个场景所需的数据,例如编译着色器、上传纹理和实例化游戏对象。此外,Unity 还会执行第一个场景中所有 MonoBehaviourAwake 回调。

这就意味着,如果在项目的第一个场景中的 Awake 回调中存在任何执行时间很长的代码,那么该代码可能会导致项目的初始启动时间的延长。解决此问题的方法是删除这些运行速度慢的代码,或者在应用程序生命周期的其他地方执行该代码。

解析运行时跟踪

对于在初始启动时间之后的性能分析跟踪记录,最需要关注的是 PlayerLoop 方法。这是 Unity 的主循环,该循环中的代码每帧运行一次。

Unity 项目示例的 Instruments 跟踪记录
Unity 项目示例的 Instruments 跟踪记录

上面的截屏说明了 PlayerLoop 中几种对性能影响最大的方法。注意:PlayerLoop 中的方法的名称可能因 Unity 版本而异。

PlayerRender 是运行 Unity 渲染系统的方法。此方法进行的操作包括剔除对象、计算动态批次以及向 GPU 提交绘图指令。任何图像效果或基于渲染的脚本回调(例如 OnWillRenderObject)也在此时运行。通常情况下,当项目进行交互时,此方法应该对 CPU 时间的消耗最大。

BaseBehaviourManager 调用三个模板化版本的 CommonUpdate。这些会调用当前场景中附加到处于活动状态的游戏对象的 MonoBehaviour 中的特定回调:

  • CommonUpdate<UpdateManager> 调用 Update 回调
  • CommonUpdate<LateUpdateManager> 调用 LateUpdate 回调
  • 如果已勾选物理系统,则 CommonUpdate<FixedUpdateManager> 调用 FixedUpdate

通常情况下,BaseBehaviourManager::CommonUpdate<UpdateManager> 是最适用于检查的方法族,因为它是 Unity 项目中运行的大多数脚本代码的入口点。

还有其他几种方法可用于检查:

  • 如果项目使用 UGUI 系统UI::CanvasManager 将调用多个不同的回调。其中包括 Unity__ UI__(即用户界面,User Interface)让用户能够与您的应用程序进行交互。Unity 目前支持三种 UI 系统。更多信息
    See in Glossary
    的批量计算和布局更新;这两种操作是 CanvasManager 出现在性能分析器中的最常见原因。
  • DelayedCallManager::Update 运行协程
  • PhysicsManager::FixedUpdate 运行 PhysX 物理系统。这主要涉及运行 PhysX 的内部代码。当前场景中物理对象(例如 RigidbodyCollider)的数量会影响 PhysX 的内部代码。基于物理系统的回调也会出现在此处,例如 OnTriggerStayOnCollisionStay

如果项目使用 2D 物理系统,那么会在 Physics2DManager::FixedUpdate 下显示为一组相似的调用。

解析脚本方法

在使用__ IL2CPP__种由 Unity 开发的脚本后端,可在为某些平台构建项目时替代 Mono。更多信息
See in Glossary
交叉编译的平台上调用脚本时,应查找包含 ScriptingInvocation 对象的跟踪行。这表示 Unity 的内部原生代码转换到脚本运行时以便执行脚本代码。注意:从技术上讲,Unity 通过 IL2CPP 运行 C# 代码后,该代码也会成为原生代码。但是,这种交叉编译后的代码主要通过 IL2CPP 运行时框架来执行方法,与手写的 C++ 不太一样。

Unity 项目示例的跟踪记录
Unity 项目示例的跟踪记录

在上面的截屏中,嵌套在 RuntimeInvoker_Void 行下的方法是 Unity 每帧执行一次的交叉编译的 C# 脚本的一部分。

跟踪行的名称是原始类的名称,后跟下划线和原始方法的名称。对于此跟踪记录示例,可以看到 EventSystem.UpdatePlayerShooting.Update 和其他几个 Update 方法。这些是 MonoBehaviours 中常见的标准 Unity Update 回调。

展开这些方法后,可以查看其中哪些方法消耗了 CPU 时间。这包括项目中的其他脚本方法、Unity API 和 C# 库代码。

上面的跟踪记录显示,StandaloneInputModule.Process 方法每帧对整个 UI 进行一次光线投射。此方法检测是否有任何悬停的触摸事件或是否激活了任何 UI 元素。迭代所有 UI 元素并测试鼠标的位置是否在UI 元素的边界矩形内的方法属于资源密集型方法。

资源加载

还可以在 CPU 跟踪记录中识别出资源加载记录。标识资源加载的主要方法是 SerializedFile::ReadObject。此方法将二进制数据流通过名为 Transfer 的方法从文件连接到 Unity 的序列化系统中。Transfer 方法位于所有资源类型(例如纹理、MonoBehaviour 和粒子系统)上。

场景加载的跟踪记录
场景加载的跟踪记录

上面的截屏是 Unity 加载场景时的跟踪记录。加载场景时,Unity 会读取并反序列化场景中的所有资源,如对 SerializedFile::ReadObject 下各种 Transfer 方法的调用所示。

如果在运行时看到性能不稳,并且性能跟踪记录显示 SerializedFile::ReadObject 使用了大量时间,则意味着资源加载降低了帧率。请注意:SceneManagerResources 或 AssetBundle API 请求同步进行资源加载时,SerializedFile::ReadObject 通常会出现在主线程上。

为解决此类性能不稳的问题,可以使资源加载异步进行(将繁重的 ReadObject 调用移至工作线程),或预加载某些繁重资源。

在 Unity 克隆对象(在跟踪记录中以 CloneObject 方法表示)时也会出现 Transfer 调用。如果在 CloneObject 调用下出现了对 Transfer 的调用,则表示 Unity 不是从存储中加载资源。相反,Unity 将旧对象的数据传输到新对象。为此,Unity 会序列化旧对象,并将结果数据反序列化为新对象。

检测所有函数调用
自定义性能分析器模块