UWA助力独立游戏开发!《江湖X:汉家江湖》性能诊断精讲!

UWA助力独立游戏开发!《江湖X:汉家江湖》性能诊断精讲!

上周,我们对当下一款高自由度的独立武侠游戏《江湖X:汉家江湖》进行了性能诊断。其画风新奇,玩法独特,并获苹果首页推荐。同时,在与该开发团队交流的过程中,UWA也发现了若干独立游戏中可能普遍存在的问题,在此分享给大家,希望也能对大家的项目起到借鉴作用。

感谢该游戏的研发团队—汉家松鼠的分享,希望通过这个游戏的性能精讲,助力更多开发者节省优化的时间,将更多精力集中在游戏的开发和制作中去。

请输入图片描述


一、快速定位性能瓶颈

我们知道,衡量一款游戏的性能主要参考CPU、内存、GPU等几大指标。而UWA性能检测工具能帮我们从中快速定位性能问题。首先,我们通过查看UWA报告的 优化任务列表 来明确目前的性能瓶颈和接下去的优化方向。
请输入图片描述
任务列表显示目前的性能问题主要集中在:

1、总体内存占用较大;
2、纹理资源内存占用过高;
3、堆内存占用较大;
4、GC调用频率过高。

由于总体内存包含了资源内存和堆内存,而GC调用频率又和堆内存分配有关,所以我们很容易得出问题主要集中在堆内存和资源内存这部分。下图为该游戏在测试的十分钟内内存的变化趋势。
请输入图片描述
下面我们就从资源内存和堆内存这两块,分别为大家说明下优化的方法。


二、堆内存占用

1.堆内存占用分析
上图可以看到该项目最大的问题是堆内存,在测试的十多分钟内达到了195MB的峰值,远远超过了UWA的推荐值50MB。堆内存的峰值一方面影响内存的大小、另一方面影响GC的CPU开销,需要大家特别注意。
请输入图片描述
上图是Mono堆内存的走势图,测试一开始就达到了150MB,这说明很有可能在启动的时候读取了大量的配置文件。我们在图中选中了某一帧,可以看到黄色线条Unused Mono已经达到了60MB,由于Android设备上,Mono堆内存一旦分配就不会返还给系统,这意味着60MB的堆内存已经白白浪费了。一般来说Unused Mono我们建议小于10MB。从这款游戏的体量来看,其Mono堆内存至少可以减去70MB。

关于堆内存的详细分配,我们可以查看Mono堆内存报告。如下图所示,我们看到该游戏在运行了十多分钟后,一共分配了211MB堆内存。点开函数的堆栈信息后发现,211MB的构成为:MapUI(90MB),UITools(74MB),ServerSelectUI(35MB),BattleField(10MB)。
请输入图片描述
我们来分析下堆内存占用较大的几个函数的开销:

1)点开每一级的函数并进行分析,在53MB的占用下面可以看到有一个堆内存分配很大的实例化操作,这就是Unity的Instantiate,在该项目中主要和Sprite或UI相关。一般来说,实例化次数越多,堆内存越高。
请输入图片描述

Q:实例化会占用堆内存的开销吗?如果Prefab特别大且复杂,那实例化出来的纹理会占用堆内存吗?

会的。Unity本身在Instantiate调用的时候就会产生一定的堆内存开销。所以我们不建议频繁地Instantiate和Destroy,因为会产生堆内存碎片。同时,实例化操作也会导致其挂载的脚本进行初始化操作,比如..ctor操作, Awake操作等等。

在此,我们建议可以通过UWA报告的“资源实例化/激活”模块来查看具体的实例化内容,下图中Instantiate Gameobject数量,有3814次,这个过于频繁,可以考虑缓存来减少实例化的操作。
请输入图片描述

2)另外一个堆内存比较大的开销是Get Data,看到是反序列化XML文件,在每次反序列化文件的时候都会有一个LoadObjectFromXML的堆内存分配。

请输入图片描述
以此类推,其他部分大家也可以通过Mono堆内存报告,逐一定位问题。

2.堆内存泄露分析
在UWA的Mono堆内存报告中,我们可以选择两帧数据(第32000帧和第5000帧)进行对比,查看是否存在堆内存泄漏。例如案例中提到:对比后发现有一个Dictionary产生的堆内存一直在涨,增长了2.92MB。我们可以对照堆栈信息中罗列的函数查看哪个Dictionary Size占用的堆内存一直在增长,是否合理,为什么没有销毁,是确实需要缓存还是泄露,从而进行修改即可。
请输入图片描述
又例如:看到在AddLocation中涨了0.7MB,其中大量的是UI的实例化,我们通过查看变量类型发现这个和UI或Sprite相关,有342个GameObject没有被释放。查看方法是:AddLocation里肯定会调用实例化,实例化出来的1700多个变量究竟放在了哪里,是另外一个池子,还是Lua里,为什么没有被卸载。
请输入图片描述
堆内存分析模块总结:大量的实例化操作和配置文件读取造成了较高的堆内存分配,可以通过UWA报告的Mono堆内存模块来进行逐一优化。


三、资源内存

资源部分包括纹理、Shader、网格、粒子系统、动画片段、音频等。该项目的资源问题主要集中在冗余以及格式的处理上。需要大家注意的是:虽然部分冗余的资源可能对内存不会造成可观的影响,但是资源的数量会直接影响加载速度。

下面我们就以这款游戏的纹理资源为例来为大家说明,可以看到测试的十多分钟内,纹理资源高达87MB,对于纹理资源的内存占用,我们建议尽可能控制在50MB以下。造成的原因很可能是 1)纹理冗余 2)纹理格式。
请输入图片描述

1.疑似冗余
在纹理资源的具体使用列表中,我们看到大量资源的数量峰值为2和3,即表示可能存在了冗余,其中有个名为“SpriteAtlasTexture-ui”的资源很可能在游戏包中存在两份,即造成了8MB的冗余。在此我们建议研发团队使用线上的资源检测与分析,通过几分钟就能确定AssetBundle中存在多少冗余的资源,通过改善资源的依赖关系和打包方式,将这部分冗余降为0。

很多朋友可能疑问,是否能够真正做到零冗余,在此我们建议大家可以参考我们之前的推文:Unity 零冗余解决方案

2.纹理格式
同时,我们也在上述资源中看到有不少较高分辨率如2048*2048、格式为RGBA16的纹理,根据名字我们推测是人物的胡子、嘴巴、鼻子等,这些我们建议美术效果允许的情况下尽可能换成硬件支持的压缩格式。
请输入图片描述

Q:一般来说,资源部分建议iOS和Android分开打包吗?

是的,我们建议这么做。本身iOS和Android的硬件压缩格式都是不一样的,我们建议iOS上尽量用PVRTC,Android上尽量用ETC。两者都是有损压缩,可能存在色阶问题,但目前来说的确没有完美的解决方案,只能尽量避免。但值得大家注意的是,如果在Android上你觉得RGBA16和ETC的效果差不多,一定要尽量用ETC格式,这样既能减少内存又能提升加载效率。大家可以参考我们之前的技术推文《加载模块深度解析之纹理篇》来加深理解。


四、CPU性能

除此之外,我们再来看看性能其他部分是否有值得完善的地方。
请输入图片描述

1.渲染模块
由于项目主要用Sprite2D来渲染,场景中几乎没有不透明的东西,所以不透明的耗时比较低,半透明耗时高主要是由于Sprite的渲染所致,大概在10-20ms。具体如何定位呢?我们在CPU时间占用页面选择Camera.Render,这个就是Unity引擎的渲染模块。
请输入图片描述

我们可以看到渲染模块的具体耗时分布:Drawing 81%(大家看到的不透明和半透明之和,其中半透明72%,不透明6%),Culling是11%,即裁剪,一般在渲染的时候都会有,我们建议控制在10%~20%,如果后续发现Culling高过20%就值得大家注意了。

所以很明显,半透明的开销很高,其中又可分为两部分,BatchRenderer.Add占了32%,BatchRenderer.Flush占了21%。BatchRenderer.Add点开后有个CanvasBatchIntermediateRenderer.RenderSubBatch,这主要是UGUI造成的开销,右边的调用次数608883其实就是UGUI的DrawCall,在1.5w帧的测试中,这相当于每一帧至少有40个Drawcall,是比较高的。
请输入图片描述
BatchRenderer.Flush点开是SpriteRender.Renderer。这也是我们上文中提到的某些场景中耗时较高的部分。这些是需要大家注意的,建议研发团队降低其渲染Draw Call来进行优化。
总结来看,SpriteRender的开销很高,在10-20ms震荡,建议通过降低Draw Call来进行优化。UI这里的DrawCall,我们建议控制在20-30以内,目前64是比较高的,而且该项目的UI并不复杂,完全可以控制在30以内,这样UI部分的耗时就能降低到3ms以下。

Q:我们人物的脸是图集捏出来的。头像也是由很多图片做成的,导致十多个DrawCall,很多东西都是来自于不同的Atlas,覆盖不同的层级,一个被另一个交叉叠着,来达到修改面部样子和表情的效果。

理论上是应该避免的,但具体的UI优化,我们目前只能通过现场查看UI的制作才能给出具体的方案。一般遵守这样的原则:尽可能把频繁使用的元素放在一起。所以一个方法是检测玩家的数据,大体上用了哪些头发、哪些眉毛等,把高频率的放在一起,但是这么做并不保险。另一个方式是动态拼合,即在运行时对玩家的选择拼合Atlas,这种方法可以让拼合更有针对性,但会有一定的性能开销。

2.UI模块
我们再说下UI方面的耗时,在分析&建议中,我们看到UI的CPU占用主要集中在0~8.9ms,这个比较高,我们建议的范围是0~5ms ,这说明:1)UI做得比较大,Canvas里面很多东西, 2)UI在频繁地重建。
请输入图片描述

关于UI模块的开销,我们也可以从UWA报告的高CPU函数中看到两个UI相关的函数:PutGeometryJobFence和WaitingForJob,如果大家的测评报告中这两项也很高,那么建议对项目中的UI系统进行大力优化。

请输入图片描述
以上就是该游戏的性能诊断,我们主要从该项目的UWA性能报告中反映的内存、UI以及渲染等问题出发,通过数据报告的查看对比,整理出了一条较为完整的优化思路,希望能对大家的自身项目有所启发。

后续,UWA也将继续为大家推送更多性能诊断的实战分享,期待大家能从根本上逐渐掌握性能优化的思路,从此优化不再盲目。

关于UWA
由侑虎科技开发的游戏/VR应用性能优化平台,目前提供 1)性能检测与优化2)资源检测与分析 两大工具。同时,我们也会为大家开发更省心的功能,希望通过它们可以减少开发者反复测试定位问题的时间,从而将更多的精力集中在项目开发和解决问题中,能为大家项目研发省下的任何一分一秒,都是UWA团队的骄傲。