Home » 未分类 » Scout性能优化实战——Array,Vector专项整治

Scout性能优化实战——Array,Vector专项整治

关于Array、Vector数组的专项整治

这一次,我们的出发点和和上次《关于Point对象的专项整治》是一样的,就是避免Array临时变量的频繁创建,从而给GC减少不必要的工作量。

同样的,先打开Scout,然后开始跑游戏。从监控的数据来看,从分配内存数量最多的项开始着手分析。

 1、iniNodeLink()方法创建的Array数量排名第一;

这里是A星寻路这块的初始化,每次切场景,才会进行分配,虽然数量巨大,但是毕竟会一直缓存,所以对GC造成负担也是一次性的,优先级就不是那么高。要想优化这点,不是没有办法,但是需要付出更多时间。

解决方案:

优化A星寻路算法,采用跳点A星寻路,可以大大减少节点数量,也可以较大提升寻路计算时间。需要进行预处理,但是预处理这块部分可以交给编辑器。技术难度会是一个重要的挑战,需要较多时间去钻研和重构代码。

 2、其次是就是filters()

调用filters()接口,每次都会new 一个数组出来,所以要避免频繁调用。这里每一帧都会调用一次filters的那个“坏蛋”就是我们FilterPlugin这个类。这个类是Tweenlite中用来,做呼吸灯渐变动画效果的。

通过断点,我们轻松的发现,这个每一帧都在计算这个动画效果的“源头”就是“MailPanelExt”。不合理的地方是:没有邮件图标需要闪烁的时候,这个方法依然会被执行。

解决方案:

MailPanelExt.refresh() 的时候,加个判断,如果有“邮件图标”则显示滤镜渐变效果,如果没有需要显示的时候,就把tweenMax对象kill()掉。这样,不仅仅是解决了filters不断产生临时数组的问题,而且解决了DisplayerList渲染的问题。如下图所示:第一张图是每一帧都在更新Filters的是情况,第二张图是不更新Filters的情况,明显displayList渲染时间要少很多。

3、继续探索,我们发现sort()被调用的次数非常频繁,导致了大量临时Array数组被分配内存。

根据Scout的是指引,我们定位到代码位置:LayeredContainer.render(),这个每帧都会执行的代码之处。原来这里主要是用来解决场景上“演员”层深排序的问题的。由于角色和怪在场景上是实时移动的,所以,不得不对他们进行排序,根据Y坐标来进行排序。也就是Y坐标越大,其在显示列表中的位置越靠后,这样就可以避免出现“角色或怪的头被别人的脚踩住”这样的BUG。

解决方案:

自己写一个sort()方法,来替代原生的sort()方法。这里我们写了两种自定义的排序算法,性能测试如下:

也就是说,在数组长度较小的情况,自定义的快速排序与原生的sort()接口,效率是没有差别的。所以,采用自定义的排序算法来替代原生的sort()还是可以试一试的,而且不会生成临时Array,内存上几乎没有消耗。为了保证通用性,我们在工具类中,会实现判断数组的长度,如果数组长度在200以内,我们会采用自定义的算法对其进行排序,如果数组长度超过200,我们就改用其原生的sort()接口。

4、与上面的sort()类似,Array.splice() 的调用也是相当广泛,而且该方法会产生临时数组对象。

解决方案:

跟第3点优化解决思路相同,采用自定义的splice方法,去取代原生的。测试过程略。在2015年9月,Flash Player 19里面,有新的API,分别是insertAt() 和removeAt()。但是要考虑兼容性,采用手写的方法来处理会比较好点,下个项目,倒是可以用新API

5、keys()与HashMap.values这两个接口,如果调用比较频繁也会创建许多临时Array对象,而且比较耗性能,因为在创建的过程会遍历Object的keys

解决方案:

这里的解决办法就是缓存keys与value. 通过一个变量标记,是否需要更新缓存。当hashMap有put()或者remove()的时候,标记缓存为需要需要更新。下次调用keys或者values的时候,会判断是否需要更新缓存。如果需要,则更新数组,否则,直接返回缓存的数组。

 6、setTimeOut(),setInterval()方法也会生成临时数组,所以要尽量避免使用它。

发现一个规律,就是但凡方法参数列表采用动态参数(…agrs)这种形式的,都极有可能创建一个临时数组。比如trace(…),setTimeOut(…),setInterval(…),Array.splice(…),Array.sort() 。为什么说是极有可能创建一个数组呢?因为我发现Array.push(…)这个方法不会。[纠正]其实push也会的,但是Sout被追踪的次数可能会出现误差。

解决方案:

尽量在设计上避免,使用setTimeOut()和setInterval() 。比如BaseAni.$deini t()这个方法,就不应该使用延迟调用的。

如果无法避免,推荐使用FrameDispatcher.setTimeOut()或者Juggler.delayCall(),两个接口,我更加推荐Starling携带的Juggler.delayCall(),因为它实现了缓存池机制,对内存更加友好。

另外,尚未验证的是,FrameDispatcher.setTimeOut()和Juggler.delayCall()也使用了动态参数列表。可能并不能从根本上解决避免临时Array的创建。最好的解决思路是,在Juggler.delayCall()的模板上,增加几个接口,固定参数数量。

7、protoBuf中toString()方法,相当麻烦,计算量比较大。从下面代码截图就可以看出,new了好几个数组对象。

解决方案:

避免使用。理论上可以试着改用toNumber() , fromNumer()来表示玩家ID ,应该没有问题。或者其他方式改写toString(),简化高低位的转化。会不会有其他问题,有待进一步研究确认。而且String对象也应该改是被避免分配的。就一个ID的事情,简单点就好,这里除了带来Array的问题,还带来了String问题,String的专题,后续再做分析。

8、当开启翅膀的功能后,getOffset()产生的临时数组Array也是数量庞大,外网版本,检测到其分配量飙升到了第4名.

解决方案:

建立缓存池,Key-value格式。这里缓存采用Dictionary 而不是Object,因为Object键值只能是String,为了避免String被频繁分配,所以,这里key采用int类型.

9、split()方法调用,数量在外网版本统计当中,50分钟内,飙升到了第2名。主要产生来源是施法特效那块。

解决方案:

将已经解析好的数据,缓存起来,而不是每次都拆解一遍。

10、getInstance().dispatchEvent() 。这里会产生临时数组。好在调用还不算太频繁,但是也优化空间。

解决方案:

弃用动态参数这种看上很方便的定义格式,(…args). 固定一个参数就够了,没有参数,传null,有一个参数直接传,有多个参数自行组装数组。

    分享到: