原文链接:https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity1.html
当讨论性能时,至关重要的是知道所有的优化尝试必须从发现过程开始。剖析一个应用程序的性能来发现其性能热点是必要的第一步,然后针对项目的技术和资源的架构对性能测试的结果进行分析。
请注意:本章在底层代码分析追踪中发现的函数名是从Unity5.3中得到的,方法名也许会在未来的版本中更改。
工具
为了进行性能分析,Unity开发者可以使用许多不同的工具。Unity有一批内置的工具,比如说CPU分析器、内存分析器和5.3中新的内存分析器。
然而,最好是从平台特定的工具中生成数据。它们包括:
·对于iOS平台:Instruments和XCode Frame Debugger
·对于Android平台:Snapdragon Profiler
·对于运行Intel CPU/GPU的平台:VTune和Intel GPA
·对于PS4平台:Razor suite
·对于Xbox平台:Pix tool
这些工具通常在那些可以利用IL2CPP来产生一个C++版本的项目中最有用处。这些底层代码提供在Mono下运行实现不了的清晰的调用栈和高精度的方法计时。
Unity已经创建了一个使用Instruments来分析IOS游戏的基础指导,请看使用Instruments分析性能(链接见原网页)。
剖析启动数据
在查看启动时间的数据时,有两个关键方法需要检查。这两个方法是配置、资源和代码这些可能影响启动时间的主要的地方。
请注意在不同平台上启动时间清单不同,在大多数平台上,它是作为一个静态的闪动屏幕对使用者可见。
(图片见原网页)
上面的截图是在一台IOS设备上运行的示例项目的Instruments的数据。在平台特定的startUnity函数中,请注意UnityInitApplicationGraphics和UnityLoadApplication函数。
UnityInitApplicationGraphics执行大量的内部工作,比如说建立图形设备以及初始化许多Unity的内部系统。另外,它初始化Resources系统。为了完成这个任务,它必须加载Resources系统中包含的文件的索引。
每个在名字叫“Resources”(请注意,这只应用于在项目Assets文件夹下名字叫“Resources”的文件夹,也包括所有那些“Resources”文件夹下的子文件夹)文件夹下的资源文件都包含在Resources系统数据内。因此,初始化Resources系统所需的时间与“Resources”文件夹下的文件数量至少呈递增的线性相关。
UnityLoadApplication包含加载和初始化项目中的第一个场景的函数。这包含反序列化和实例化所有显示第一个场景所必须的数据,比如编译shader,上传纹理和实例化游戏物体。此外,此时也执行所有第一个场景中的MonoBehaviour的Awake()回调函数。
这些流程意味着如果有任何长时间运行的代码在一个项目第一个场景的Awake回调函数中,那么这个代码对拖慢此项目的初始启动时间负有责任。要解决这个问题要么牵扯到消除这些执行慢的代码,要么就在应用程序生命周期函数的其他地方执行这些代码。
剖析运行时数据
对于在初始化启动时间之后分析运行数据快照,最主要的关注点是PlayerLoop函数,这是Unity的主要循环,其中的代码每帧运行一次。
(图片见原网页)
上面的截图来自于运行在Unity5.4示例工程的性能分析工具,举例说明一些PlayerLoop中一些最应该注意的函数。请注意在不同Unity版本的PlayerLoop中的函数名也许会不一样。
PlayerRender是运行Unity渲染系统的函数。这包含剔除对象,计算动态批处理,并且向GPU提交绘制指令。任何图像效果或者基于渲染的脚本回调(例如OnWillRenderObject)都在这里运行。通常,在项目交互时,它应该是CPU时间的最大消费者。
BaseBehaviourManager调用CommonUpdate的三个模板化的版本。其执行当前场景中激活的游戏物体上挂载的MonoBehaviour中的某些回调函数。
·CommonUpdate<UpdateManager>调用Update回调
·CommonUpdate<LateUpdateManager>调用LateUpdate回调
·CommonUpdate<FixedUpdateManager>如果物理系统被使用,则调用FixedUpdate回调
总体来说,BaseBehaviourManager::CommonUpdate<UpdateManager>是最值得关注的函数组,因为它是运行在一个Unity项目内大多数脚本代码的入口点。
还有其他的一些函数值得关注:
UI::CanvasManager,如果一个项目使用UGUI它会执行一些不同的回调。这包括UGUI的batch计算和布局更新,这两个操作总会使CanvasManager出现在profiler中。
DelayedCallManager::Update,运行协程。这在本文档的协程章节中有详细的描述。
PhysicsManager::FixedUpdate,运行PhysX物理系统。这主要是包含运行PhysX的内部代码,并且被当前场景中物理对象的数量所影响,比如说刚体与碰撞体。并且,基于物理的回调也经常出现在这里-特别是OnTriggerStay和OnCollisionStay。
如果项目使用2D物理,那么在Physics2DManager::FixedUpdate下会出现类似的一组调用。
剖析一个脚本的函数
当一个脚本在IL2CPP交叉编译的平台上执行时,查找包含一个ScriptingInvocation对象的数据行。这是Unity内部的底层代码为了执行脚本代码转换到脚本运行时的点(请注意,从技术上讲,在被通过IL2CPP运行过之后,C#/JS脚本代码也会变成底层代码。然而,这种交叉编译代码主要通过IL2CPP运行框架执行函数,而且从严格意义上来讲也不想手写C++那样)。
(图片见原网页)
上面的截图是从一个运行在Unity5.4的示例项目的其他一些数据中截取的。所有嵌套在RuntimeInvoker_Void这一行下的函数都是交叉编译的C#脚本的一部分,它们被每帧执行一次。
这些一行行的追踪数据相当容易阅读,每一个前面都是原始的类名后面加上下划线跟上原始的函数名。在这个示例追踪数据中,可以看到EventSystem.Update,PlayerShooting.Update和其他一些Update函数。这些是MonoBehaviour中标准的Unity的Update回调。
通过展开这些函数,可以在它们中间准确的知道每个函数所花的CPU时间。这也包括项目中其他脚本的函数,Unity的API以及C#库中的代码。
上面的追踪数据显示StandaloneInputModule.Process函数每帧向全部UI发射一次射线,以检测是否有任何触摸事件发生在任何激活的UI元素上。主要的消耗是迭代所有的UI元素,并且测试鼠标的位置是否在它们的矩形边界内。
资源加载
资源加载也可以在CPU跟踪数据中找到,表明加载资源的主要函数是:SerializedFile::ReadObject。这个函数通过一个名字叫Transfer的函数将一个文件的二进制数据流与Unity的序列化系统相连接。Transfer函数可以在所有的资源类型中找到,比如说纹理,MonoBehaviour和粒子系统。
(图片见原网页)
在上面的截图中,一个场景被加载。这需要Unity读取并反序列化场景中的所有资源,在SerializedFile::ReadObject之下显示着被调用的各种Transfer函数。
一般来讲,如果一次性能卡顿被观测到发生在运行时,并且性能追踪数据显示大量的时间花在了SerializedFile::ReadObject上,那么就是由于资源加载原因导致的帧率下降。请注意,在大多数情况下,只有当使用SceneManager, Resources或是AssetBundle的API时同步资源时SerializedFile::ReadObject才会在主线程中出现。
这种性能卡顿可以常规的方式解决:您可以异步加载资源(移动大型的ReadObject到工作线程),或是可以预加载某些大型的资源。
请注意当克隆对象时Transfer也会发生调用(显示在追踪数据的CloneObject函数中)。如果在CloneObject下出现Transfer调用,那么资源不会从硬盘中加载。而是从旧的对象数据传输到新的对象。为此,Unity序列化旧的对象并且将结果数据反序列化成一个新的对象。
网友评论