GC的调用频率接近1000,目前看下来数据是比较合理的;另外GC的耗时在371ms,这个数值相对来说偏高。要优化CG耗时主要是通过两种方法:1)降低调用频率,即尽可能跑更多的帧再调用,2)降低GC每次的耗时。两种优化的方式不太一样,我们分别说明:
1)降低调用频率可以通过减少GC累积的分配数据来实现。我们可以跳转至代码效率-堆内存使用的页面。
这些是堆内存累积的分配量,堆内存分配越少游戏图片素材,则GC触发也越少。可以看到, 排名前两个函数的累积分配加起来有200MB左右,这里有大量的提升空间。第一个函数UIPanel.LateUpdate是NGUI的函数,优化的方法并不是通过优化代码,而是尽可能优化NGUI网格重建的频率;第二个函数UERoot.Update是大家的主逻辑,需要通过代码函数去定位堆内存的分配,现在我们已经有Mono堆内存测试功能,大家可以通过Mono报告去看具体函数的开销,定位起来相对起来会容易得多(后文将详细说明)。
总而言之,GC调用频率的优化主要通过这个面板去找累积堆内存分配最高的函数,一点点去优化。
2)降低GC的耗时可以通过优化堆内存的峰值来实现。GC调用一次的开销和堆内存里的对象有关,即对象越多、峰值越高,则GC越高。该项目接近100MB的堆内存峰值是较高的,我们推荐降低到40MB范围以内。
堆内存的峰值一方面影响内存的大小、另一方面影响GC的CPU开销,需要大家特别注意。
其他模块的性能较为正常,在此不多做说明。
内存模块
在UWA报告的内存模块来看,在测试的十多分钟内,内存峰值达到378MB,堆内存峰值达到92.6MB,都是属于比较偏高的,下面我们将通过内存模块的几大构成来逐一分析。
1. 堆内存
在堆内存的走势图中我们发现两个情况:首先是刚刚测试的时候就分配了68MB左右的堆内存,另一方面是堆内存的不断增长,最终达到了92MB。
关于前者,多数是配置文件占用的空间比较大,或者缓存机制所致;针对后者,在这里建议大家可以参考UWA的Mono报告中高堆内存留存函数列表。如下图演示,通过点击右边的“蓝色箭头”,可以查看某些函数中生成的驻留在内存中的详细变量情况,从而能更快地判断和定位堆内存的泄露点。
2. 资源内存—纹理
除了堆内存,我们再来看下资源内存。纹理的内存占用峰值为91MB,这个值在我们测试过的大量项目中看是属于偏高的。到底是哪些资源占了那么多的空间?我们可以跳转到UWA报告的具体资源信息中来各个击破。在这些资源的属性中,我们先来关注下数量峰值,如下图,我们可以看到不少资源的数量峰值出现了2、3等数值, 即相同的资源在测试包中出现了两份、三份。
同时,我们发现五页左右的纹理都存在冗余两份的情况,其中不少是100KB的纹理资源的情况,所以造成了不少纹理资源的浪费,建议大家通过UWA资源检测工具来查看AssetBundle中是否有冗余,接下来检测代码,是否有反复加载和反复Unload导致纹理依然残留的情况。此外,从纹理资源的格式上来看是比较正常的,基本上用了各个平台支持的格式。
3. 资源内存—网格
网格的内存峰值在24MB,相对合理,但从走势上来看也会有持续向上的趋势,数量稍微偏高。同样,网格资源也存在一定的冗余问题,如下图所示。
除了这张数量峰值,我们刚刚提到Color和Tangent属性,这两个属性对于大多数的Shader来说都用不上,所以需要研发团队进一步确认,是否有开启不必要的顶点属性。
Q:Tangent是否在NGUI中会被用到?
A:默认不会开启,最多生成normal。一般情况下是有color,但是没有normal和tangent。
4. 资源内存—动画资源
动画资源的数量也是比较多的,因此内存是明显偏高的。虽然存在一种可能,即如果大家采用了动画模块的缓存机制,的确会不断上涨,但建议研发团队也确认下这部分能否优化。毕竟报告中的数值相当高,足以引起大家重视了。
Q:为什么资源会有冗余的情况?
A: AssetBundle之间本身有冗余,它们分别被加载进来后就会产生冗余;卸载后再加载,也会产生冗余。
Q:这个无法达到没有冗余的吧?
A:可以做到零冗余,主要控制好管理的机制。1、避免AB本身没有冗余;2、通过管理的方式提前知道这个纹理是否已经被加载等。在UWA博客中有几篇相关的技术文章,大家可以参考下。
5. 资源内存—Shader
Shader的内存一向都是比较小的,这里主要还是看数量峰值,因为Shader资源数量会造成Shader.Parse函数的开销。在UWA报告的“重要参数解析”一栏中,可以看到频繁出现了56-60ms的峰值,这些都是Shader的解析造成。因此Shader的冗余可以理解成Shader.Parse的使用频率更多了。检查冗余的办法和上述的一样。
资源管理
在内存篇中,我们看到总体内存峰值为378MB,其中资源内存峰值将近227MB、堆内存的峰值92.6MB,那么剩余的将近60MB内存占用去哪儿了呢?这时,我们千万不能忽略这两大杀手:WebStream和SerializedFile。
1)WebStream内存占用
WebStream为Unity 5.3 以前版本的项目,通过特定API(new WWW、CreateFromMemory等)加载AssetBundle文件所开辟的较大块内存。主要用于存放AssetBundle的原始数据和解压后数据。
2)序列化信息内存占用
Unity引擎的序列化信息种类繁多,其中最为常见且内存占用较大的为 SerializedFile。在Unity 5.3之前的版本中,该序列化信息的内存分配主要为项目通过特定API(、CreateFromFile等)加载AssetBundle文件所致。
对于这部分的优化,这就要结合UWA的另一项黑科技—资源管理。我们可以在该模块里看到加载资源时的总次数、加载方式、加载耗时等具体信息。
从表中看,AssetBundle加载的频率是比较一致的,基本上都是十几次,并且有一定间隔,但是大家可以注意到有些AssetBundle重复加载的频率比较高,如下图中连续三次加载,这可能就是存在一些问题,比如缓存时间过短等等。
另外可以通过UWA报告中的具体AssetBundle使用情况模块查看AssetBundle在内存中的驻留情况,如下图所示。大家在Unity 4.x 版本上用的是WWW,所以内存中具有一定的WebStream的占用,加载的AssetBundle比较多,WebStream也会较大。目前来看,AssetBundle最高值13个,在合理范围之内。
可以看到,资源加载主要是通过AB.load和AB.loadasync两个API,这个加载次数是比较合理的。
同时,我们也能通过该面板查看到具体的资源加载和卸载的情况:
资源实例化
做实例化操作的时候会有明显的开销,对于一些元素的SetActive的时候也会有开销。比如实例化的时候被操作了上百次,研发团队需要考虑是否在战斗中频繁出现,有这些元素存在的话,我们建议在关卡前做一次预加载,之后用到的时候通过缓冲池进行激活、禁用等等,来减少实例化的开销。
该项目SetActive操作比较高,总共有16万次的操作,我们看下频率较高的几个。
我们看到Skillicon元素 (蓝色线)在战斗过程中有持续的SetActive的操作,由于我们是每十帧汇总一次数据,所以每帧就会有5 个的调用,所以大家要特别注意这些元素,如果只是个空的Object,那么SetActive的开销是非常小的,但是如果这个元素身上带了些组件,大家需要确认下这些组件身上是否存在一些每帧都先禁用然后通过某些条件再打开的的一些操作,导致一些问题。
所以从资源管理的面板中,我们可以看到AssetBundle加载、驻留、资源实例化和激活等情况,帮助我们把加载部分做得更流畅。一旦资源加载和实例化发生在战斗中,那么峰值基本上是难避免,现在我们看到相对比较合理的方法是:实例化操作在战斗刚刚开始时候发生,然后随着战斗的时间慢慢加长,后面的实例化时间应该尽可能避免掉3D角色,利用缓冲池的方法等去优化。
GPU性能
最后,我们看来下该游戏在三星S6上的GPU的耗时情况。
1. Overdraw
我们主要关注填充倍数均值,4.0x 我们可以认为在测试的过程中,平均每一帧的像素会被填充4次,该值较高,一般我们建议把这个数值控制在3.0x左右。
虽然Overdraw总体问题不算大unity资源优化,但是我们也会通过曲线去找一下是否有比较高的地方。比方说某些区域的填充倍数会到18的情况,从对应的画面来看,可能是和BOSS战斗,或者进入一些区域的时候,看上去屏幕上的特效比较多,在峰值区域特效比较多,所以画面会比较亮,其实Overdraw较高在多数情况下就是因为半透明特效比较多、区域比较大所致。
另外 UI界面展开的情况下也会比较大 。这种情况下如果游戏时间比较长,GPU的负载比较高,导致发热比较快。所以Overdraw比较高的地方需要大家关注,特别是持续时间比较长的界面,类似UI界面(因为UI都是半透的,所以Overdraw会叠起来)。对此,我们一般建议减少UI和其背后场景的重叠,比如下图中UI后面的场景还在正常进行的(后面的人物和背景都看得到),大家可以考虑下能否在全屏UI出现后把相机关掉,这样的话可以减少不必要的Overdraw开销。
最后还有一些面积很大的特效,从截图上来看还好出现的时间比较短,所以优化的优先级略低。
以上就是该游戏的诊断内容,我们主要从性能的几大核心指标:CPU、内存、GPU三大模块反应的性能问题出发,通过数据报告的查看对比,整理出了一条较为完整的优化思路,希望能对大家的自身项目有所启发。也感谢该团队的分享unity资源优化,这也是鉴于我们相信这些数据的公开,能帮到更多游戏开发者省下优化的时间,将更多精力集中在游戏的开发和制作中去。