在你分析应用程序时,有一些常见的你可能会遇到的问题。此页面概述了调查一些常见性能问题原因的方法。
在查看启动时间跟踪时,有两种关键方法要检查:UnityInitApplicationGraphics
和 UnityLoadApplication
。这两个方法是项目配置、资产和代码可以在其中影响启动时间的两个主要位置。
请注意:你的应用程序的启动时间在不同平台上各不相同。在大多数平台上,启动在 启动画面 出现时发生。
在上图中,这是在 iOS 设备上运行的范例 Unity 项目的 Instruments 跟踪,在特定于平台的 startUnity
方法中,请注意 UnityInitApplicationGraphics
和 UnityLoadApplication
方法。
UnityInitApplicationGraphics
执行很多内部工作,例如设置图形设备并初始化很多 Unity 内部系统。它还通过加载包含在 Resources 系统中的所有文件的索引来初始化 Resources 系统。
Unity 的 Resource 系统 包括项目 Assets
文件夹中的 Resources
文件夹中的数据里的每个 asset 文件。这包括 Resources
文件夹的子文件夹中的任何文件。因此,初始化 Resources 系统所需的时间随着你应用程序项目 Resources
文件夹中文件的增加而增加。
UnityLoadApplication
包含用于加载和初始化项目中第一个 场景场景包括游戏环境和菜单。可将每个独特的场景文件视为一个独立等级。在每个场景中,你可以布置你的环境、障碍物和装饰,从本质上来说是以分块的方式设计和构建你的游戏。 更多信息
参见 词汇表 的方法,其中包括反序列化和实例化显示第一个场景所需的数据,例如编译 着色器在 GPU 上运行的程序。 更多信息
参见 词汇表、上传纹理以及实例化 游戏对象Unity 场景中的基本对象,可以表示角色、道具、场景、摄像机、导航点等。游戏对象的函数由附加到它的组件定义。 更多信息
参见 词汇表。此外,Unity 会执行第一个场景中所有 MonoBehaviour
的 Awake
回调。
这些流程意味着,如果某个项目第一个场景中的 Awake
回调存在任何长时间运行的代码,则该代码可能导致项目初始启动时间变慢。解决此问题的办法是消除慢速代码,或在应用程序的生命周期中的其他位置执行该代码。
对于在初始启动时间之后捕获的性能分析跟踪记录,最主要的关注点是 PlayerLoop
方法。这是 Unity 的主循环,其中包含的代码每帧运行一次。
上方的屏幕截图展示了 PlayerLoop
中对性能产生最大影响的几个方法。注意: 在 PlayerLoop
中,方法的名称在不同的 Unity 版本之间可能不同。
PlayerRender
是运行 Unity 渲染系统的方法。其中包括剔除对象、计算动态批处理以及向 GPU 提交绘制指令。所有图像效果或基于渲染的脚本回调(例如 OnWillRenderObject)也会在此处运行。一般来说,当项目具有交互性时,这应该是 CPU 时间的最大耗用方。
BaseBehaviourManager
调用 CommonUpdate
的三个模板版本。这些模板版本在当前场景中的活动游戏对象附加的 MonoBehaviour
内调用某些回调
CommonUpdate<UpdateManager>
调用 Update
回调CommonUpdate<LateUpdateManager>
调用 LateUpdate
回调CommonUpdate<FixedUpdateManager>
在物理系统已滴答时调用 FixedUpdate
一般来说,BaseBehaviourManager::CommonUpdate<UpdateManager>
是最有用的一类方法来检查,因为它是 Unity 项目中运行的大部分脚本代码的入口点。
还有其他几个方法可供检查
UI::CanvasManager
会调用几个不同的回调。这包括 Unity 的UI(用户界面)允许用户与你的应用程序进行交互。Unity 目前支持三个 UI 系统。了解更多CanvasManager
在Profiler(分析器)一个窗口,帮助优化你的游戏。它显示在游戏的各个区域花费了多少时间。例如,它可以报告在渲染、制作动画或在你的游戏逻辑方面花费的时间百分比。了解更多DelayedCallManager::Update
运行协程。PhysicsManager::FixedUpdate
运行 PhysX 物理系统。这主要涉及运行 PhysX 的内部代码。当前场景中的物理对象的数量,例如Rigidbody
和Collider
,会影响 PhysX 的内部代码。基于物理的回调也出现在此处:尤其是 OnTriggerStay
和OnCollisionStay
。如果项目使用的是 2D 物理,那将显示为Physics2DManager::FixedUpdate
下的类似调用。
当脚本允许你创建你自己的组件的代码,触发游戏事件,构建组件属性,并以你喜欢的任何方式响应用户输入。 了解更多
参见术语表在交叉编译与IL2CPPUnity 开发的脚本后端,在你为某些平台构建项目时,你可以将它用作 Mono 的替代方案。 了解更多
参见术语表的平台上被调用时,查找包含 ScriptingInvocation
对象的跟踪行。这是 Unity 的内部原生代码转换为脚本运行时环境来执行脚本代码。注意:从技术上讲,在 Unity 通过 IL2CPP 运行你的 C# 代码之后,它也变为原生代码。但是,此交叉编译代码主要通过 IL2CPP 运行时框架执行方法,而不是类似于手工编写的 C++。
在上图中,嵌套在 RuntimeInvoker_Void
行下的方法为交叉编译的 C# 脚本的一部分,Unity 会在每帧执行一次。
跟踪行的名称为原始类名称,后跟下划线和原始方法的名称。在此示例跟踪中,你可以看到EventSystem.Update
、PlayerShooting.Update
以及其他几个Update
方法。这些是大多数MonoBehaviours
中常有的标准 Unity Update
回调。
你可以展开这些方法,查看它们中的哪些方法消耗了 CPU 时间。其中包括项目中的其他脚本方法、Unity API 和 C# 库代码。
上面的跟踪显示 StandaloneInputModule.Process
方法每次帧都会对整个 UI 进行一次射线投射。此方法检测是否有任何触摸事件悬停于任何 UI 元素之上或激活任何 UI 元素。在所有 UI 元素上进行迭代并测试鼠标的位置是否在其边界矩形内的方法会消耗大量资源。
你还可以识别 CPU 跟踪中的资源加载。指示资源加载的主要方法是 SerializedFile::ReadObject
。此方法连接来自文件中的二进制数据流到 Unity 的序列化系统,该系统通过名为 Transfer
的方法进行操作。Transfer
方法存在于所有资源类型,例如纹理、MonoBehaviour 和 粒子系统通过在场景中生成和动画化大量的 2D 图像来模拟液态、云和火焰等流体实体的组件。 更多信息
参见 术语表。
上面的屏幕截图是 Unity 加载场景的跟踪。当加载一个场景时,Unity 读取并反序列化场景中的所有资源,这由 SerializedFile::ReadObject
下方的对各种 Transfer
方法的调用表示。
如果你在运行时看到性能抖动,并且性能跟踪显示 SerializedFile::ReadObject
使用了大量时间,则意味着资源加载降低了帧率。注意:当 SceneManager
、Resources
或 AssetBundle API 请求同步资源加载时,SerializedFile::ReadObject
通常出现在主线程中。
要解决此性能抖动,你可以使资源加载变成异步(将繁重的 ReadObject
调用移动到工作线程),或者预先加载某些繁重的资源。
在 Unity 克隆对象(在跟踪中由 CloneObject
方法表示)时也会出现 Transfer
调用。如果 Transfer
调用出现在 CloneObject
调用的下方,则 Unity 并没有从存储中加载资源。相反,Unity 将旧对象的数据传输到新对象。为此,Unity 将旧对象序列化,并将生成的数据反序列化为新对象。