我们用50次游戏性能的深度优化,总结出了五条“毒鸡汤”

作者:jcmp      发布时间:2021-04-18      浏览量:0
原文链接: https://blog

原文链接: https://blog.uwa4d.com/archives/deepanalysis.html。

一、答案永远在现场

这些是创业20个月以来,我们从游戏团队那里听到最多的几句话。上文所说的报告,是指我们侑虎科技的项目深度分析与优化方案报告。之所以写这篇文章,是因为我们即将完成我们的第50次游戏项目的深度优化。是的,你没看错,20个月50次,意味着平均一个月2.5次。平均每份报告100页、2.3万字。

对于一款游戏项目的深度优化,我们一般需要10天左右的时间全身心投入,其中包括2~3天的现场排查。这是因为很多时候,只有进驻现场查看代码,设身处地地与研发团队进行沟通,我们才能找到问题背后的真正答案。

举个例子,UI优化是一项非常复杂的工作。两个视觉表现完全一致的界面,其底下的性能开销可能完全不一样。究其原因,是其本身在Editor中的制作手法不同导致。

“动静分离”是我们经常提到的UI优化解决方法,但是知易行难,难在如何定位到底哪些动态元素造成了较高的开销,如何判断哪些开销是可以避免的,如何分离这些UI元素等等。所以,我们驻场的一个主要工作就是检测其UI界面的制作方式,将有问题的UI元素全部找出,并针对不同类型的UI元素给出不同的解决方案。同时,我们还会现场对UI界面进行一定的修改,告诉大家通过合理的优化后,项目的UI耗时应该是如何变化的。

下图就是我们在一款二次元ARPG项目中,UI优化前后WaitingForJob的性能对比开销。

再举个例子,堆内存泄露是大家项目中经常遇到的问题,这也是我们驻场时经常花费大量气力去解决的问题。 一方面,我们需要通过阅读研发项目中的大量代码去定位可能存在的泄露点,因为很多时候,内存泄露都是因为Object被“到处索引”所致。另一方面,我们需要研发团队的配合,对代码进行完善并快速出包验证,这样反复调试后,泄露问题就能基本得到解决。

下图是我们对某项目的堆内存泄露问题优化前后的对比图,从发现到解决,一共不到24小时。

如果没有到现场,我们也许到现在都不知道为什么下图中的赛车(红框处)已经和外围的围栏和斜坡产生了碰撞,尽管实际上它们还相距很远。

如果没有到现场,我们也许到现在也无法告诉研发团队,如何仅用两小时,将下面的UI面板从38个Draw Call优化到9个Draw Call。

如果没有到现场,我们也许至今都未发现几个简单的CharacterController的center赋值会如此耗时。

如果没有到现场,我们也许至今都无法解释,在场景切换的过程中,为何会出现如此大量的“空闲”时间。

类似的如果非常多,所以我们必须到现场。答案在哪里,我们就在哪里。

二、别放过任何一个可疑的地方

别放过任何一个可疑的地方,因为那很可能是提升你项目效率的绝佳机会。

举个例子,我们在做UI优化时会做一个常规测试,就是把游戏静静地放在那里,不去操作,看看UI性能的变化。 可以看到,在UI界面静止不动时,其对应函数的CPU占用仍然有2 3ms的开销。虽然耗时不高,但这已属于不正常的情况。所以,在到达现场后,我们详细检测了其耗时的具体根源,最后发现元凶是聊天框中的emoji表情:每隔1 2s都会转动一次,但由于其本身挂在一个很大的Canvas上(下图右),导致了每次emoji表情一改变,就会使得这个含有1804个顶点的Canvas完全重建一次,从而造成了较长的时间开销。

上述这种操作,也是UI界面中动静元素没有分离的一种典型例子。

三、空杯心态,经验归零

我们正处于一个高速发展的时代,每个人的知识和经验在整个行业面前都可以说是微不足道。我们做过的优化越多,就越发现我们已知的渺小。有那么几个项目,我们在进驻团队前以为已经大致摸清了优化的方向,但等到了现场,面对他们的代码时,才发现我们之前推测的都是错的,一切都要推倒重来。

举个例子,下图是一款竞技类手游的动画系统在真机上的CPU耗时。可以看到,其每隔几帧,就会有一个20~30ms的CPU开销。

这显然是不正常的,进驻团队前,我们认为很可能是该项目中手动调用了一些Animator的API,按照经验来看,检查下代码应该很快就能定位问题。但没有想到的是,我们花了大量时间都未发现任何一行与动画模块相关的可疑代码。与项目团队的交流也否定了我们之前的想法。我们开始怀疑是不是我们的检测工具出了问题,但是项目中通过其他方式获取的运行帧率却又和这个开销非常吻合。更诡异的是,即便我们把动画角色屏蔽了,该耗时依然存在。这个时候,我们开始隐约觉得这个问题很可能不是出在动画模块上了。

既然问题不按套路出牌,那我们也就不能按照常规方法来解决了。第二天我们在开发团队的协助下,一起逐步对项目的各个模块进行拆分,看看这个问题到底出在哪里。具体经过曲折坎坷,就不赘述了。结果令我们相当惊讶,该CPU耗时居然与通过Android原生代码获取游戏的电量、Wifi Ping值的操作相关。当我们把该操作关闭后,一切都恢复了正常。下图就是正常的动画模块开销情况。

这个问题,我们后来在自己内部复盘时仍然觉得匪夷所思。我们也有对相关代码尝试进行复现,但是在我们的空项目中,一切正常。

这个例子不过是我们在现场遇到的众多幺蛾子问题之一。随着这些经验的累积,我们也渐渐发现,影响性能的已经不再仅仅是大家的代码编写问题、引擎API的使用问题,还会和所用的引擎版本、所处的硬件、OS版本,甚至设备的运行状态息息相关。

如果你耐着性子看到了这里,我敢说你肯定也遇到过以下这些情况:

诸如此类的不确定性问题,在当今的项目研发中比比皆是。如何解决?将心态放空,将经验归零,多做实验,以数据说话。

四、没有标准的优化方案

我们经常会被问到:渲染的耗时怎么下降、UI模块怎么优化、如何提升加载效率等问题。对此,我们的回答往往是:

举个例子,渲染模块开销高,其优化的前提是一定要定位清楚,究竟是什么造成它这么高的耗时,瓶颈是不透明渲染、半透明渲染、裁剪、图像后处理还是其他?只有定位准确了,才能制定出行之有效的优化方案。正所谓,对症下药,才能药到病除。

再举个例子,大家都知道Draw Call越高,开销越大。但是否越低越好呢?当然不是。UI Draw Call就是一个很好的例子。在一个项目中,我们建议研发团队将UI的整体Draw Call从当前的9拆成20。研发团队很不理解,DrawCall多了不是耗时更多?确实如此。但是该团队为了降低Draw Call将大量UI元素拼合在一起,虽然渲染开销下降了,但UI重建的开销却大大提升了。在我们来看,当UI Draw Call拆到20时,虽然渲染模块的开销可能会平均增加1ms,但UI重建的平均开销则会下降5ms。从整体性能而言,至少能有4ms的提升。

五、压力越大、收获越大

性能优化本身就是一件很难的事,它一般需要优化人员具有很扎实的功底、很全面的基础以及很丰富的实战经验。而如果要在一个完全陌生的项目中,十天内就给出一份详尽的性能优化方案,则更是难上加难。

回想我们这么多次的现场实战中,没有一次是顺风顺水的,在每个项目中都会遇到一些意想不到的技术问题。甚至有几次,我们内部已经做了决定:“如果今天晚上这个问题还是找不到根源,我们就退款走人”。这就是我们每一次都要面对的压力。幸运的是,每一次我们都跨了过去。同时,每一场深度优化后,我们都会进行复盘,这次优化中哪些地方还有待提升,就尝试能否继续打磨我们的优化工具,缩短优化的时间;哪些地方做得很高效,就看看能否复制到线上的测评工具中去,让更多团队受益。

当然,我们每次收获的不止是经验,更重要的是又多了一些信任我们的朋友。彼此的信任,让我们的脚步能够涉足更多的地方,看到并解决更多的技术问题。

曾经有位朋友和我说,“优化的方法茫茫多,然而针对于我们项目的优化方法在千种万种中可能只有一个”,这也许是大部分游戏从业者的痛点。所谓”优化“,能够解决“你的问题”才是优化。

PS:时间过的好快,一晃创业已经二十个月了。下图是2015年因为一个感触给自己加的一条梦想。现在在机场、火车站、地铁上甚至是电梯里都可以时不时看到我们优化过的游戏了。所以梦想还是要有的,万一实现了呢!