本文为 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 格式,以降低内存占用。

请输入图片描述
补充说明:在图表中我们也看到不少 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 粒子系统内存(数量和内存超标)
请输入图片描述
排查方向:

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 优化

最后还需要关注的是 GPU 问题,UWA 在测试的时候发现该游戏在某些高端设备上耗电发热明显。
请输入图片描述
在 GPU 性能的 Overdraw 页面,我们可以看到 GPU 信息如下图所示:
请输入图片描述
下图为该帧下的截图,可以看出,特效和 UI 的屏幕占比过大,且存在 UI 叠层的概率较高。因此,研发团队需要特别关注技能特效和 UI 界面的制作情况,避免给 GPU 造成大量的计算浪费。
请输入图片描述

五、优化任务队列总结

1)AssetBundle 的依赖打包方式

解决资源内存占用过大的问题

2)Mono 堆内存分配

解决 Mono 堆内存大小和 GC 卡顿的情况

3)Mesh 特效

解决渲染模块的耗时问题和 GPU 压力过大问题

以上就是该游戏的诊断内容,我们从 UWA 的性能简报出发,围绕 CPU、内存、GPU 三大模块的性能问题展开,通过数据报告的查看对比,整理出了一条较为完整的优化思路,希望能对大家的自身项目有所启发。目前,UWA 的线上测评服务已经为数千款项目提供了解决方案,我们希望能帮到更多游戏开发者节约优化的成本,提升项目的性能表现力,迎接 “精品” 时代的挑战!


↙↙↙阅读原文可查看相关链接,并与作者交流