性能优化
Q1:Draw Call和Setpass Call,这两个指标主要是看哪一个?关于这点众说纷纭,很多地方都是说看SetPass Call,但是在UWA的性能测试中,还是把Draw Call当成唯一指标。
在 Unity 5.x 中,SetPass Call与 Draw Call相比,SetPass Call的指标与性能相关性更大(比如Static Batching的开启不影响Draw Call数,而SetPass Call通常会明显下降)。但 SetPass Call在某些情况下也同样存在问题,比如往一个场景中添加任意个相邻且材质相同的大网格物体(使Dynamic Batching失效)时,SetPass Call并不会变化。因此在UWA中,我们所使用的是类似Profiler 中 Total Batches 这一项指标,通常该数值与 Frame Debugger 中的数值基本一致,因此可以通过该工具来查看每个Batch的内容,从而更有针对性地进行优化。
性能优化
Q2:我看到UICamera.Update()的GC调用特别高,只要我一移动就会产生2.8K的GC,看起来是NGUITools.FindInParents这个方法导致的,有没有什么可以优化的方法呢?
在 Editor 下,当调用GetComponent() 且 T组件并不在当前的GameObject 上时,确实会出现GC Alloc,但这在发布后是不会出现的,因此建议在真机上做一个验证。这是因为在Editor下,Unity的MissingComponentException实现所致,在出现以上情况时,Unity 并不是直接返回一个 NULL,而是返回一个代理 Object用来储存一些相关信息,在后续被访问时可以给出更详细的报错信息。
性能优化
Q3:这个批渲染是什么?好像开销很高 。
从图中看出,这是场景中不透明物体的渲染开销。建议研发团队对当时场景中的不透明物体(地形、建筑等)进行进一步检测,主要查看其三角面片数是否过高、Shader是否过于复杂等。
性能优化
Q4:GameObject.Instantiate()每实例化一个GameObject到场景中,会造成卡顿,有什么办法可以优化吗?就算我采用了异步加载,仍然会有稍许的卡顿感。除了缓存池,是否还有别的方法?
建议研发团队先通过Unity Profiler来确定该性能卡顿的位置。如果只是一个空的GameObject,Instantiate实例化是很快的。一般来说,Instantiate实例化时间较长,主要由以下三个原因:
(1)与资源的加载有关:对于这种情况,研发团度需要精简资源,或者预加载资源来降低实例化的开销;
(2)序列化信息比较多:当GameObject上的Component比较多时,其Instantiate实例化性能会受到影响,比如说粒子系统,这种情况就只能通过分帧实例化,或者通过缓存池来避免;
(3)自定义组件的Awake:在Instantiate实例化时,其GameObject上挂载脚本的Awake函数也会被触发,其中产生的CPU占用,也会被计算在Instantiate实例化内。
性能优化
Q5:关于GC优化,Unity官方文档中有如下两个推荐的方法,请问在手游上是否适用?(1)频繁GC,隔几秒主动调用一次,保证不需要的堆内存能够及时释放。适用于对内存总量较小的情况。(2)尽量避免GC,可以考虑先撑大内存。
上述两种方法在目前的移动游戏中并不适用。
(1)中频繁地调用GC是否会降低GC的CPU开销吗?理论上会,但实际上,即便是降低后的GC开销也会导致频繁的卡顿。下图为UWA上一款实际测评项目在红米Note2上频繁调用GC的开销。可以看到在红框处,其GC调用非常频繁,但是其CPU占用并没有因频繁调用而降低。
(2)中尽可能避免GC是非常推荐的,但撑大堆内存的方式是欠妥当的。最好的方式是通过降低不必要的堆内存分配来尽可能避免的GC的到来。UWA建议GC的调用频率应该在1000帧/次以上。具体的做法建议查看我们之前的技术推送:Unity内存的深度剖析。
性能优化
Q6:如下图所示,什么情况下会触发MaskableGraphic.Enable() ?现在我得到的结论是只要用到了Image组件,然后禁用启用带有这个组件的物体,都会触发进而产生GC。大家都怎么显示隐藏图片这种需求的,SetActive这种方式容易产生GC。
在UGUI中,Image组件并没有重写其父类的OnEnable函数,所以在激活时会出现MaskableGraphic.OnEnable。其中出现堆内存开销的话,通常是因为其父类函数Graphic.OnEnable中,UGUI在进行全局容器的Add等类似的操作时,遇到了扩容等产生堆内存的操作。
总之,在UGUI中,UI元素的激活和禁用所导致的堆内存分配,通常是不会持续出现的,其实不需要特别地处理。但对于其CPU开销,在UI元素数量较大时,依然是可观的,所以我们依然建议,对于激活禁用操作较为频繁的UI元素,可以尝试通过移出屏幕,缩放为0等方式来避免SetActive的调用。
性能优化
Q7:在一个场景中有很多小物件,例如花花草草之类的静止物体,造成Draw Call数量较高。在不想让美术重新出资源的前提下,我是想把相同模型的东西合并,然后把小贴图打成一个大的贴图,重新算UV。还有比这种方法更好的优化手段吗?
对于场景中的静态物体,可直接尝试通过Unity的Static Batching方式来进行网格的合批,可以有效地降低由于Draw Call(5.x下的Batches)带来的性能开销。当然,Static Batching的前提是网格模型的材质相同,所以不仅需要保证Shader一致,还需要将各自的小纹理合成大纹理,从而保证Static Batching的顺利进行。
同时,如果渲染的网格面数较高,可以通过SimpleLOD、SimplyGon等常用网格简化工具对其进行快速减面。
性能优化
Q8:我游戏场景里有些草,勾选了Batching Static 如下图:
查看FrameDebug可以看到已经生成了Static Batch:
按道理来说这是的DrawCall应该是2,但是查看Profiler的时候却显示Draw Call为7,这是怎么回事呢?
从Unity 5.0开始,Static Batching的合批机制就已经出现了变化,不再进行索引数组的合批,因此并不会使得Draw Call降低,而是会降低Batches和SetPassCall,因此从图中来看,Static Batching 开启后的统计数据是没有问题的。也因此,UWA在统计时,使用的就是Batches的数值。具体的原因可见Unity官方在论坛中的回复:
https://forum.unity3d.com/threads/regression-feature-not-bug-static-dynamic-batching-combining-v-buffers-but-not-draw-calls.360143/
性能优化
Q9:求教一个屏幕后处理的问题。我们期望角色不受屏幕后处理影响,所以目前采用双相机的方案,根据Layer划分,但是这种情况下角色的影子也就没有办法投影到地表上,请问这种需求有什么好的实现方式?
可以尝试根据地表模型的局部细节来动态生成接受阴影的网格(比如Fast Shadow Receiver插件),这样既可以保证地表模型进行屏幕后处理操作,同时也可以生成相关角色的动态阴影。
性能优化
Q10:我的项目在一帧中进行实例化操作时,会产生大量的GC Alloc,请问这是为什么?我又该如何避免呢?
Unity引擎的Instantiate操作本身是会触发少量的堆内存分配,从图中数值可以看出,该堆内存分配过大的主要原因可能有以下两点:
Instantiate在该帧中调用次数过多,从图中可以看到,在一帧中Instantiate实例化操作被调用128次;
Instantiate实例化后,其GameObject身上的代码组件在执行Awake函数和构造函数(.ctor)时也很有可能产生堆内存,从目前的1.3MB堆内存来看,很有可能是这种原因。
对此,建议研发团队在Unity Profiler中直接查看Instantiate的自堆栈信息,即可定位其具体的堆内存出处。