本文为 UWA 侑虎科技原创文章,如需转载,请注明出处:https://blog.uwa4d.com
优化工作向来是个复杂系统的工程,且不说前期的框架预设、大量的版本迭代、美术和性能的需求平衡,单从每个版本的上万帧测试数据中定位真正的性能瓶颈,其工作量就已可想而知。
无论项目是否复杂或简单,当我们开展优化工作前,这些问题都需在胸中有丘壑。那么如何井然有序地定位和排查性能问题呢?我们定位到了性能的真正瓶颈了吗?今天小编以一个优化前期的 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 格式,以降低内存占用。
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 粒子系统内存(数量和内存超标)
排查方向:
1) Active 的粒子系统数量过多,UWA 建议在中低端设备上不超过 50 个,可以考虑通过一些设备分级的策略做改善(剔除某些在部分机型上表现力较差的粒子系统)
2) 缓存的数量过大(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 问题,UWA 在测试的时候发现该游戏在某些高端设备上耗电发热明显。
在 GPU 性能的 Overdraw 页面,我们可以看到 GPU 信息如下图所示:
下图为该帧下的截图,可以看出,特效和 UI 的屏幕占比过大,且存在 UI 叠层的概率较高。因此,研发团队需要特别关注技能特效和 UI 界面的制作情况,避免给 GPU 造成大量的计算浪费。
1)AssetBundle 的依赖打包方式
解决资源内存占用过大的问题
2)Mono 堆内存分配
解决 Mono 堆内存大小和 GC 卡顿的情况
3)Mesh 特效
解决渲染模块的耗时问题和 GPU 压力过大问题
以上就是该游戏的诊断内容,我们从 UWA 的性能简报出发,围绕 CPU、内存、GPU 三大模块的性能问题展开,通过数据报告的查看对比,整理出了一条较为完整的优化思路,希望能对大家的自身项目有所启发。目前,UWA 的线上测评服务已经为数千款项目提供了解决方案,我们希望能帮到更多游戏开发者节约优化的成本,提升项目的性能表现力,迎接 “精品” 时代的挑战!