Unity3D_记一次CPU卡顿的排查实践

发表于2016-08-12
评论0 8.4k浏览

这是一次CPU卡顿的排查实践,开发环境是 Unity5.3.4p5 + uGUI

======

为了避免打log引起的额外消耗,所以以下分析基于log关闭后的Profiler数据

Debug.logger.logEnabled = false;

 

问题1ConfigUtil.Get().Single(Func)

List  ConfigUtil.Get() 内含 List.AddRange()申请临时内存,相对GC Alloc和耗时较多;

T  List.Single(Func)多次遍历查找同一个List,耗时较多。影响范围较广

修行状态的红点计算NinjaPracticeController.UpdatePracticeState()较为复杂,查询修行配置有多层循环嵌套,而且内层嵌套有多次重复计算,且频繁出现于回包中。比如心跳回包会计算修行红点状态,其他一些回包触发OnSyncSthChangeCommand的也会引起修行红点计算。

修改前(60+60次):

说明: http://km.oa.com/files/post_photo/670/287670/0c60b24909bd5a2829657b7ecede05a61467795806.jpg

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_1_w1565_h736.jpg

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_2_w682_h342.jpg

将查询全量配置提前到for循环之前算好。并且classEvolveCfgclassUpgradeCfg这两项配置信息并不总是需要,而是根据底下if-else的条件局部需要,所以挪到实际需要的地方才计算。

修改后(40+3次):

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_3_w1269_h659.jpg

Single方法的调用还可以进一步优化(配置List转换为Map,然后for循环内按Key查找)。

更新忍者状态的NinjaController.UpdateNinjaState也有类似的现象:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_4_w834_h277.jpg

初次加载忍者技能、忍者课程修炼也是如此:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_5_w737_h293.jpg

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_6_w726_h243.jpg

其他模块的List  ConfigUtil.Get()调用,建议按需修改。

 

问题2MaouOutline的数组重置操作

为达到更佳的描边效果,重写了ModifyMesh/ModifyVertices的实现,而MaouOutline脚本又频繁出现在各个界面。影响范围较广。

修改前:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_7_w1408_h907.jpg

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_8_w705_h323.jpg

发现主要消耗是在重设数组长度。将vertexs数组初始化时指定初始长度,性能可小幅度提升。

经实测,List<UIVertex> vertexs = new List<UIVertex>(256);

指定初始长度为256,比128512或其他都好一丢丢(减少了SetCapacityArray.Resize)的消耗,但同时增加了List构造函数的消耗)。

第一次修改后:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_9_w1404_h903.jpg

进一步修改,调整为静态全局数组,就不会每次都重置数组长度了:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_10_w769_h323.jpg

第二次修改后:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_11_w1404_h974.jpg

MaouOutlineModifyMesh/ModifyVertices已经没问题了,但是Unity自带的OutlineModifyMesh仍然有重置数组长度的现象。

经排查,发现TaskItem有两个按钮Text使用了Outline而非MaouOutline。于是将所涉及prefabOutline脚本全部替换成MaouOutline脚本即可。

Outline替换为MaouOutline后:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_12_w1404_h974.jpg

到此GC Alloc基本解决了(0.9M=>22K)。

但进一步又发现,剩余的耗时问题集中到了ModifyVertices(每次ModifyVertices调用都会触发8ApplyShadow调用),此问题待解决。

 

问题3GameObject.SetActive(ture/false)引起的毛刺,即使并没有实际改变true/false的状态,也会有额外的消耗。

建议封装一个静态方法:先判断GameObject.activeSelf当前状态,然后再作GameObject.SetActive(ture/false)修改状态。

对于在业务View初始化方法中只作一次GameObject状态初始化的,可以不做修改。

对于操作时会变更状态的(比如赌小游戏滚动前后的按钮状态切换、奖励展示切换),建议增加判断。

已封装为UIUtil.SetActive(GameObjectgo, bool v),可直接使用

修改前:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_13_w1187_h636.jpg

修改后:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_14_w1186_h739.jpg

赌小游戏已修改,其他模块可按需修改。

比如排行榜的一个条目:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_15_w858_h306.jpg

 

问题4B.GetView=>B.OnShow=>A.OnHide

界面切换会有新界面(B)的GetViewOnShow,以及旧界面(A)的OnHide,而这里的多次SetActive(true/false)会引发多个脚本OnEnable/OnDisable

现象:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_16_w926_h772.jpg

一次SetActive(true/false)会引发上百次的OnEnable/OnDisable

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_17_w703_h229.jpg

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_18_w728_h258.jpg

两个建议:

1)对于Instantiate创建的VIew,无需默认设为SetActive(false),因为马上就OnShow=> SetActive(true)了。

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_19_w1088_h385.jpg

2)对于需要频繁显示和隐藏的常驻VIew(比如TitleBar之类的),OnShowOnHide中除了使用SetActive(true/false)外,也可考虑position移入移出屏幕,或者设置Scale10等等。

但是需要注意,应该增加忽略隐藏状态下业务逻辑的代码。

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_20_w648_h537.jpg

 

问题5:可疑的Graphic.Rebuild=>Text.OnPopulateMesh=>Font.CacheFontForText

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_21_w932_h795.jpg

初始化或更换Text.text的文本,会引发Text.OnPopulateMesh,有时还会引起Font.CacheFontForText=>TextUpdateGeometry(耗时)和Text.cachedTextGeneratorGC Alloc)。

Image也有类似现象。优化方法待思考。

 

问题6:可疑的Tx.OnUpdate

这里的大毛刺使得战斗开场那一帧比较卡,看到界面显示一个空的战斗场地,等一两秒后才有人物跳出来。

由于Profiler信息量太大,无法使用DeepProfiler查看详情。

通过添加Profiler代码定位,最后定位到Tx.OnUpdate()里的tx_object_unity_update();,然后线索就断了。

真实原因待查。

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_22_w1262_h708.jpg

添加了Profiler辅助方法:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_23_w1140_h371.jpg

其中加载了许多图标,包括忍者头像、战斗上阵的人物等。如果原因是资源尺寸过大,可考虑能否裁剪成多张小尺寸的。

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_24_w279_h273.jpg说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_25_w279_h277.jpg

 

问题7:可疑的Text. preferredWidth

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_26_w961_h559.jpg

在普通副本和精英副本切换时,发现有一个可疑的Text. preferredWidth,它会触发Text.cachedTextGeneratorForLayout,从Profiler看似乎是申请了一个长度较大的List

经走读代码发现该行代码实际是不需要的,于是直接注释掉。

另全量搜索发现其他脚本也多处出现Text. preferredWidth,还需逐一排查。

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_27_w1124_h337.jpg

 

问题8:可疑的Component.GetComponentFastPath

进入某些列表界面(比如任务界面),初始化MaouGrid组件时会调用SetParent

SetParent会触发一些Unity自有脚本(比如MaskableGraphic)的OnEnable/OnDisable的成倍调用,耗时和GC Alloc累积起来也比较可观。

切换SetActive(true/false)也有类似现象。

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_28_w935_h708.jpg

展开MaskableGraphic.OnEnable/OnDisable,最后落到Component.GetComponentFastPath,此方法耗时和GCAlloc原因待查。

 

问题9 debug信息输出导致的额外损耗

VersionView每一帧Update()都在刷新帧率,导致过渡频繁的字符串FormatConcat

建议修改为StringBuilder,而固定字符串(版本信息字符串、网络状态字符串)可改为分别用单独的UI.Text显示,每次只刷新变化的数据(比如帧率)。

优先级较低,暂时可忽略,且正式版本会屏蔽

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_29_w1084_h615.jpg

另一个可疑的debug信息(PrettifyProto.ToStrLogUtil.WritePre),常见于一些回包中,也可以考虑处理一下:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_30_w643_h170.jpg

 

问题10:界面加载和界面刷新时的耗时

界面加载时有较多的资源加载。比如通灵兽界面:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_31_w885_h551.jpg

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_32_w1041_h164.jpg

又比如任务界面:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_33_w1300_h783.jpg

领取任务奖励后,任务条目更新又有一些耗时的刷新操作:

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_34_w1025_h863.jpg

经分析,这里每次都会先Hide(全部),然后Show(其中某几个),可考虑优化。

 

问题11:切换普通副本和精英副本,会有瞬间的白屏

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_35_w1711_h929.jpg

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_36_w1269_h724.jpg

 

问题12:图标加载时有额外的字符串操作消耗。

比如副本介绍界面的奖励图标加载(除了字符串操作,还有一个可疑的IdUtil.TryGetId):

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_37_w747_h580.jpg

或者任务条目的奖励图标查询(除了字符串操作,还有一个可疑的Behaviour.set_enabled):

说明: http://avocado.oa.com/fconv/files/201607/dee66cda8de7eefa8f79990a5a4c0862.files/doc_image_38_w849_h489.jpg

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引