优化工作向来是个复杂系统的工程,且不说前期的框架预设、大量的版本迭代、美术和性能的需求平衡,单从每个版本的上万帧测试数据中定位真正的性能瓶颈,其工作量就已可想而知。
无论项目是否复杂或简单,当我们开展优化工作前,这些问题都需在胸中有丘壑。那么如何井然有序地定位和排查性能问题呢?我们定位到了性能的真正瓶颈了吗?今天小编以一个优化前期的MOBA手游的UWA性能测评报告为例,深入分析该项目在真机上的运行情况,并讲解如何通过报告中的功能模块更高效地定位性能瓶颈,希望能对大家的项目有所参考。
一、关于性能简报
在这次优化讲解的时候,我们将从报告的“性能简报”模块入手(每个项目性能报告的首页),该模块作为UWA性能评测报告中的一大模块,将引擎性能模块的数据更有序、更核心地汇总成一个完整的优化体系,建议大家在优化项目之初先仔细参阅该模块的数据。点击这里可以快速了解UWA性能简报。
在性能简报的第一个模块,可以直观查看到最近测试的项目报告中的重要参数、其优化效果和性能变化趋势。
从性能排名的模块中,我们提供了测评项目在国内数千款测评项目中,同设备和同类型的横向排名,从而帮助大家更客观地发现项目中的优势和不足。
UWA解读:该项目在CPU模块和内存上均有大量的提升空间。CPU模块的压力主要来自于引擎的渲染和UI,物理、粒子和动画,逻辑代码较为严峻。内存模块中各个资源的占用内存普遍都较高,Mono堆内存分配也较大。
在性能简报的最下方,是UWA针对本次性能提测所提供的分析与建议,在这里将所有引起以上参数开销的性能问题逐一列出,并通过精准的页面链接跳转定位到问题源头,协助开发者进行有效地排查。其中,标红部分的问题较为明显,建议研发团队优先处理。
优化任务队列是我们提供给用户的性能优化切入点,下面我们将逐一分析上图中罗列的问题。
二、优化队列详剖析之内存篇
2.1 内存占用
本次报告的内存占用峰值为660.3MB,高于行业同类型同设备,当前行业均值为305MB,因此需要研发团队密切关注。特别的,如果要保证在一些1GB内存的设备比如iPhone 6或6 Plus上不闪退,我们建议尽量不要超过280MB。
内存的主要构成来自资源内存和Mono堆内存,在此我们做下详细分析。
2.1.1 资源内存之纹理 (内存超标)
排查方向:
1) 纹理的分辨率、格式
如性能简报中显示,RGBA32和ARGB32都不是Android原生支持的格式,不但内存占用较大,且加载效率也低下,建议尝试转成ETC1等硬件支持的格式。如果效果不满意也可以尝试通过Dither算法,对某些高精度的纹理进行处理,转成RGBA4444或RGB565格式,以降低内存占用。
补充说明:在图表中我们也看到不少N/A资源:一般是new出来没有命名的纹理资源,建议在这些纹理new出来之后,给.name赋值,以方便进行纹理资源管理。
2)在查看纹理的具体使用情况时,我们同时发现其冗余情况较为严重。下图为按峰值倒序排序的资源列表截图,可以看到数量峰值大于1的纹理有100多个,当两张纹理的名字、尺寸、格式等属性都相同,UWA就会将其视为具有高风险冗余的资源。
关于冗余的排查优化方法:
a) 检查AssetBundle(即打包的时候就有冗余)中是否有冗余,该问题可通过AseetBundle资源检测服务来定位;
b) 检查AssetBundle重复加载情况,是否有AseetBundle反复加载导致的缓存漏洞。
2.1.2 资源内存之网格 (内存超标)
排查方向:
1)表格中顶点数大于5000的属于超大网格,需要进一步排查是否有必要。
按Vertex数量进行排序,可以看到第一页的网格顶点数都在1w左右,这个会造成一定程度的内存压力。
2)上图中也可发现,大量网格含有Tangent属性,Tangent属性一般为导入引擎时生成,会增大网格的内存占用,如果实际上不需要Tangent属性,可在Inspector面板中修改设置,关闭Tangent导入选项。
列表中显示Tangent数量为-1的网格,表示Read/Write选项被关闭了,所以统计不到。在此,UWA建议研发团队提交一个临时的打开该选项的版本,来检查网格的属性是否合理。
3)存在冗余的问题,解决方法同纹理
另外,我们在排查资源清单的时候发现了大量的内置资源,在这里提醒大家关于内置资源的处理方法可参考这篇文档: 零冗余解决方案(其他类型的资源可借鉴)
2.1.3 动画资源(内存超标)
根据性能简报的提示页面转到动画文件的使用信息列表,如下图所示:
对于大于200KB且时长较短的动画资源,UWA认为可以进一步排查。这里看到资源列表按照内存占用排序,前3页内存占用大小都超过了200KB,建议使用一些精度处理的方法来进行优化:通过降低浮点数精度的方法来减小动画文件的大小。需要注意的是,这种方案会导致动画精度一定程度上的损失,需要大家衡量下结果。
2.1.4 粒子系统内存 (数量和内存超标)
排查方向:
Active的粒子系统数量过多,UWA建议在中低端设备上不超过50个,可以考虑通过一些设备分级的策略做改善(剔除某些在部分机型上表现力较差的粒子系统)
-
缓存的数量过大(UWA建议不超过600个),如下图中实际粒子的使用走势图所示,蓝线表示内存中的粒子系统数量,紫线表示实际上Active的量,从而得知大量粒子系统始终没有Active过(表格中显示Active数量峰值为0),但在运行时都被加载了。
在下图中,对于始终未被Active的粒子系统,我们建议研发团队针对配表优化。
2.1.5 材质(数量超标)
对于Material资源的把控主要在于数量而非内存,UWA建议材质数量控制在300以内。
排查方向:冗余
(1)对于Instance类型的冗余Material资源,两种优化方法:
1)尝试通过《使用MaterialPropertyBlock来替换Material属性操作》方法对其进行优化;
2)当我们修改了一些特定GameObject的资源属性时,引擎会为该GameObject自动实例化一份资源供其使用,比如Material、Mesh等。以Material为例,我们在研发时经常会有这样的做法:在角色被攻击时,改变其Material中的属性来得到特定的受击效果。这种做法则会导致引擎为特定的GameObject重新实例化一个Material,后缀会加上(Instance)字样。其本身没有特别大的问题,但是当有改变Material属性需求的GameObject越来越多时,其内存中的冗余数量则会大量增长。一般情况下,资源属性的改变情况都是固定的,并非随机出现。比如,假设GameObject受到攻击时,其Material属性改变随攻击类型的不同而有三种不同的参数设置。那么,对于这种需求,我们建议直接制作三种不同的Material,在Runtime情况下通过代码直接替换对应GameObject的Material,而非改变其Material的属性。这样,成百上千的Instance Material在内存中消失了,取而代之的则是这三个不同的Material资源。
(2)对于非Instance类型的冗余Material,建议研发团队通过资源检测服务来查看AssetBundle中是否存在冗余资源。
以上是主流资源的使用情况和分析建议,下面我们再关注下报告中检测到的其他资源的使用情况:
2.1.6 字体(内存超标)
主要问题:冗余
2.1.7 Shader
排查问题和关注方向:
1)Shader冗余
2)部分常用的Shader是否可以做成常驻的,考虑做法是Shader加载后放在脚本中,引用之后不卸载;
3)Shader的内存占用往往不会造成明显的问题,但是数量如果过大,容易导致Shader.Parse在中低端上的高耗时;
4)资源列表中查到有不少Standard Shader,如下图所示,由于其会带来不必要的Shader计算开销,从而大幅增加GPU中对应像素的计算压力。对此,建议研发团队尝试对其用普通的Shader来替换。
2.2 Mono 堆内存
2.2.1 单次分配过大
报告显示部分函数的Mono堆内存单次分配较大,建议大家通过详细的堆内存报告中定位堆内存分配过大的时刻。这里推荐用UWA GOT针对部分函数进行打点拆分。
2.2.2 堆内存泄露
经分析,报告也存在堆内存泄露的风险,我们通过对比相同游戏场景(均为副本)的不同采样点,发现某些函数存在堆内存驻留的问题,如下图运行到16000帧和4000帧时,游戏场景均为战斗副本中,但是函数Asset.WaitReadFileBywww有1.99MB的堆内存驻留,经过与研发团队讨论,初布判断这里疑似泄露。
其他函数也存在堆内存泄露的情况,建议研发团队逐一排查。
三、优化队列详剖析之引擎篇
3.1 渲染模块
渲染模块的开销较大,我们建议直接从堆栈信息中定位。通过性能简报提示,我们查看Camera.Render的堆栈信息如下所示,其中蓝色底纹的是需要大家关注的重点部分,即主要的渲染开销瓶颈。
RenderForwardAlpha.Render是指半透明渲染的耗时,在这层函数中,我们看到有两个高耗时的函数:
1)Mesh.DrawVBO和CreateVBO
这个函数在半透明下,就表示绘制半透明网格的耗时,半透明网格一般就是UI、场景里的半透明物件,还有半透明的Mesh特效。如果只是一帧其实问题不大,但如果是频繁开销,则需要研发团队特别注意。
经和研发团队的交流讨论,我们了解到的确存在Mesh特效过大的情况,因此我们建议是否可以设定一些设备分级的策略。
2)Material.SetPassFast
Material.SetPassFast是Unity引擎在渲染过程中Material的轮循切换开销,一般在Unity 5.0~Unity 5.3版本中出现(项目版本是Unity 5.3.5)。根据下图中的堆栈走势可发现这种情况是几乎每一帧都发生的,有渲染的地方就会有Material的切换。
从问题图中可以看出,在运行的21000帧中,Material.SetPassFast一共被调用118万次,频率较高。建议研发团队在报告中的具体信息页面查看材质是否有过多“冗余”的材质出现(本文2.1.5中有提及),尽可能降低材质的使用冗余度。
3.2 UI模块
从图表中可以发现UIPanel.LateUpdate()的重建较高,这块需要大家关注下UI制作是否合理,由于这块内容和研发团队的制作方法紧密相关,需要具体问题具体分析。所以详细的交流探讨细节我们不在此展开,建议同样有着这方面困惑的开发者可以参阅UWA Blog上关于UI的视频:Unity UI性能优化,这里我们提到了面对多种UI性能优化的技巧。
3.3 物理模块
从UI模块可知,研发团队使用的是NGUI,在每个Panel上都放置了一个Rigidbody,所以当UI Widgets摆在同一深度并存在相互叠加的情况时,会造成较多不必要的Contacts。建议研发团队看下是否能把UI层之间的碰撞检测关掉。
另外,在Unity 5.3.5版本上,如果想进一步降低物理模块的开销,在完全没有使用物理的情况下,可以将Fixed Timestep设置为0.05或0.1均可,降低它被调用的频率。同时,尽可能优化其他模块耗时,让每帧的总体耗时尽可能降低。另外,需要注意的是,修改Fixed Timestep也会影响FixedUpdate的调用,在修改之前一定要检测项目中是否有使用FixedUpdate。
3.4 粒子模块
粒子系统的耗时多与Active的粒子数量有关,这块建议结合粒子模块的内存问题(文中2.1.4有提及)一并优化。
四、GPU优化
最后还需要关注的是GPU问题,UWA在测试的时候发现该游戏在某些高端设备上耗电发热明显。
在GPU性能的Overdraw页面,我们可以看到GPU信息如下图所示:
下图为该帧下的截图,可以看出,特效和UI的屏幕占比过大,且存在UI叠层的概率较高。因此,研发团队需要特别关注技能特效和UI界面的制作情况,避免给GPU造成大量的计算浪费。
五、优化任务队列总结
1)AssetBundle的依赖打包方式
解决资源内存占用过大的问题
2)Mono堆内存分配
解决Mono堆内存大小和GC卡顿的情况
3)Mesh特效
解决渲染模块的耗时问题和GPU压力过大问题
以上就是该游戏的诊断内容,我们从UWA的性能简报出发,围绕CPU、内存、GPU三大模块的性能问题展开,通过数据报告的查看对比,整理出了一条较为完整的优化思路,希望能对大家的自身项目有所启发。目前,UWA的线上测评服务已经为数千款项目提供了解决方案,我们希望能帮到更多游戏开发者节约优化的成本,提升项目的性能表现力,迎接“精品”时代的挑战!