文档

Unity Engine

Unity Engine

分析性能分析器跟踪记录

Investigate common startup and runtime performance issues by analyzing Profiler traces.
阅读时间9 分钟最后更新于 1 个月前

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

解析启动跟踪

在查看启动时间的跟踪记录时,需要关注两个关键方法:
UnityInitApplicationGraphics
和
UnityLoadApplication
。这两个方法是项目的配置、资源和代码可能影响启动时间的主要原因。
**注意:**应用程序的启动时间因平台而异。在大多数平台上,启动发生在启动画面出现时。

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

在上面的截屏(在 iOS 设备上运行的 Unity 项目示例的 Instruments 跟踪记录)中,在特定于平台的
startUnity
方法中,请注意
UnityInitApplicationGraphics
和
UnityLoadApplication
方法。
UnityInitApplicationGraphics
执行大量内部工作,例如设置图形设备和初始化 Unity 的大量内部系统。它还通过加载资源系统中包含的所有文件的索引来初始化资源系统。
Unity 的资源系统在其数据中包含了项目
Assets
文件夹中
Resources
文件夹中的每个资源文件。其中包括
Resources
文件夹的子文件夹中的所有文件。因此,初始化资源系统所需的时间与应用程序项目的
Resources
文件夹中的文件数量呈正相关关系。
UnityLoadApplication
包含加载并初始化项目的第一个场景的方法。其中包括反序列化并实例化显示第一个场景所需的数据,例如编译着色器、上传纹理和实例化游戏对象。此外,Unity 还会执行第一个场景中所有
MonoBehaviour
的
Awake
回调。
这就意味着,如果在项目的第一个场景中的
Awake
回调中存在任何执行时间很长的代码,那么该代码可能会导致项目的初始启动时间的延长。解决此问题的方法是删除这些运行速度慢的代码,或者在应用程序生命周期的其他地方执行该代码。

解析运行时跟踪

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

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 的批量计算和布局更新;这两种操作是
    CanvasManager
    出现在性能分析器中的最常见原因。
  • DelayedCallManager::Update
    运行协程。
  • PhysicsManager::FixedUpdate
    运行 PhysX 物理系统。这主要涉及运行 PhysX 的内部代码。当前场景中物理对象(例如
    Rigidbody
    和
    Collider
    )的数量会影响 PhysX 的内部代码。基于物理系统的回调也会出现在此处,例如
    OnTriggerStay
    和
    OnCollisionStay
    。
如果项目使用 2D 物理系统,那么会在
Physics2DManager::FixedUpdate
下显示为一组相似的调用。

解析脚本方法

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

Unity 项目示例的跟踪记录

在上面的截屏中,嵌套在
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 和粒子系统)上。

场景加载的跟踪记录

上面的截屏是 Unity 加载场景时的跟踪记录。加载场景时,Unity 会读取并反序列化场景中的所有资源,如对
SerializedFile::ReadObject
下各种
Transfer
方法的调用所示。
如果在运行时看到性能不稳,并且性能跟踪记录显示
SerializedFile::ReadObject
使用了大量时间,则意味着资源加载降低了帧率。**请注意:**当
SceneManager
、
Resources
或 AssetBundle API 请求同步进行资源加载时,
SerializedFile::ReadObject
通常会出现在主线程上。
为解决此类性能不稳的问题,可以使资源加载异步进行(将繁重的
ReadObject
调用移至工作线程),或预加载某些繁重资源。
在 Unity 克隆对象(在跟踪记录中以
CloneObject
方法表示)时也会出现
Transfer
调用。如果在
CloneObject
调用下出现了对
Transfer
的调用,则表示 Unity 不是从存储中加载资源。相反,Unity 将旧对象的数据传输到新对象。为此,Unity 会序列化旧对象,并将结果数据反序列化为新对象。