# 分析性能分析器跟踪记录

> Investigate common startup and runtime performance issues by analyzing Profiler traces.

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

## 解析启动跟踪

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

\*\*注意：\*\*应用程序的启动时间因平台而异。在大多数平台上，启动发生在[启动画面](/engine/6000.0/manual/unity-editor/editor-features/comp-manager-group/class-player-settings/splash-screen.md)出现时。


**在 iOS 设备上运行的 Unity 项目示例的 Instruments 跟踪记录:**
![](/api/media?file=/engine/6000.0/media/images/UnderstandingPerformanceinUnity-ProfilingSection_image_0.png)

在上面的截屏（在 iOS 设备上运行的 Unity 项目示例的 Instruments 跟踪记录）中，在特定于平台的 `startUnity` 方法中，请注意 `UnityInitApplicationGraphics` 和 `UnityLoadApplication` 方法。

`UnityInitApplicationGraphics` 执行大量内部工作，例如设置图形设备和初始化 Unity 的大量内部系统。它还通过加载资源系统中包含的所有文件的索引来初始化[资源系统](/engine/6000.0/manual/analysis/understanding-performance/resources-folder.md)。

Unity 的[资源系统](/engine/6000.0/script-reference/unity-engine/resources.md)在其数据中包含了项目 `Assets` 文件夹中 `Resources` 文件夹中的每个[资源文件](/engine/6000.0/manual/working-in-unity/assets-and-media/asset-workflow/asset-types.md)。其中包括 `Resources` 文件夹的子文件夹中的所有文件。因此，初始化资源系统所需的时间与应用程序项目的 `Resources` 文件夹中的文件数量呈正相关关系。

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

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

## 解析运行时跟踪

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


**Unity 项目示例的 Instruments 跟踪记录:**
![](/api/media?file=/engine/6000.0/media/images/UnderstandingPerformanceinUnity-ProfilingSection_image_1.png)

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

`PlayerRender` 是运行 Unity 渲染系统的方法。此方法进行的操作包括剔除对象、计算动态批次以及向 GPU 提交绘图指令。任何图像效果或基于渲染的脚本回调（例如 [OnWillRenderObject](/engine/6000.0/script-reference/unity-engine/mono-behaviour/on-will-render-object.md)）也在此时运行。通常情况下，当项目进行交互时，此方法应该对 CPU 时间的消耗最大。

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

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

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

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

* 如果项目使用 [UGUI 系统](https://docs.unity3d.com/Packages/com.unity.ugui@latest)，`UI::CanvasManager` 将调用多个不同的回调。其中包括 Unity UI 的批量计算和布局更新；这两种操作是 `CanvasManager` 出现在性能分析器中的最常见原因。
* `DelayedCallManager::Update` 运行[协程](/engine/6000.0/manual/scripting/object-oriented-development/coroutines.md)。
* `PhysicsManager::FixedUpdate` 运行 PhysX 物理系统。这主要涉及运行 PhysX 的内部代码。当前场景中物理对象（例如 `Rigidbody` 和 `Collider`）的数量会影响 PhysX 的内部代码。基于物理系统的回调也会出现在此处，例如 `OnTriggerStay` 和 `OnCollisionStay`。

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

## 解析脚本方法

在使用 IL2CPP 交叉编译的平台上调用脚本时，应查找包含 `ScriptingInvocation` 对象的跟踪行。这表示 Unity 的内部原生代码转换到脚本运行时以便执行脚本代码。\*\*注意：\*\*从技术上讲，Unity 通过 IL2CPP 运行 C# 代码后，该代码也会成为原生代码。但是，这种交叉编译后的代码主要通过 IL2CPP 运行时框架来执行方法，与手写的 C++ 不太一样。


**Unity 项目示例的跟踪记录:**
![](/api/media?file=/engine/6000.0/media/images/UnderstandingPerformanceinUnity-ProfilingSection_image_2.png)

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

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

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

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

## 资源加载

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


**场景加载的跟踪记录:**
![](/api/media?file=/engine/6000.0/media/images/UnderstandingPerformanceinUnity-ProfilingSection_image_3.png)

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

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

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

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