Unity移动游戏性能优化:(二)再谈缓存池,缓存池到底能放什么?
今天来研究下缓存池在内存有限度的今天,到底能放些什么?在这之前,我们先问两个问题:
1、缓存是要解决什么问题啊?
缓存是要减小创建GameObject对象的时间消耗。
2、那我们的对象创建到底耗在了哪里呢?
在Unity下,基本可以概括为4个部分:资源读取,对象创建,动态component的添加,逻辑初始化。
一、资源读取:
假设所有的文件都在本地的话。资源读取就是把prefab的信息(从硬盘)读取到内存中来,有同步和异步两种方式。同步的资源读取,都是非常耗的。
如下图,没有预加载时,创建一个子弹的profiler截图
可以看到最耗的GetBulletPrefab中,都是资源读取相关的函数(这个函数调用了Resource.Load)。就是他,严重拖慢比赛进度。
而这个问题,显然是不能用异步读取来解决的(等三秒在发子弹?)只有预先读取并存储。
二、对象创建:
资源Load好了,那我们要真正创建对象了。这时,我们一般都是调用Object.Instantiate()来创建对象。这一步也是需要耗时的,如下图,游戏很多资源读取完毕,开始游戏时,大量的调用Instantiate()的消耗:
最要命的是,资源读取,最多就一次。而对象的创建,却是多次的。在某些极端情况下(满屏弹幕?找策划)大量的Instantiate瞬间就卡住了。
理论上,Instantiate类似对象构造函数,主要开辟内存填基本数据。按理说,这时候Awake函数等都不会在这时候调?(参考:http://answers.unity3d.com/questions/826877/awake-called-after-i-activate-object-not-after-ins.html 有不同理解的吗?)为什么会耗呢?
不过这个问题,还是可以通过缓存来解决(预加载加预创建)不过这时候就要算算需要预创建多少个了?
三、动态component的添加:
同为子弹,都需要有collider,这个可以直接在prefab里做好。但是某些子弹不需要被毁坏,就不用rigidbody(敌人身上有),有些有需要被其他子弹打,所以需要rigidbody。这就导致某些component需要动态的添加。
四、逻辑初始化
其实逻辑初始化和上一步没有严格的时序关系和区别。经常是在逻辑初始化后,才判断需不需要相应的component的,比如上一步的rigidbody。
五、花费?
相对较少,优化空间也小。逻辑很多情况下当然也是可以优化的,但是不在这个"缓存池”的讨论范围内。
小结一下比较下个部分特点:
资源读取:耗时最长,与资源(模型,贴图等)关系紧密,与游戏逻辑关系松散。
对象创建:耗时较长,与资源关系松散(作为prafab创建时已经不用关心具体是什么资源了),与游戏逻辑有关(需要判断要创建的对象数量)。
动态component和逻辑初始化:耗时不一定,游戏逻辑密切相关。
好,回到一开始的问题:缓存到底要缓存什么?
1、资源:在内存许可的情况下,尽量预加载所有资源。目的是生成对象的时候,至少不用读取硬盘。
2、对象:在内存许可的情况下,尽量多创建到缓存。目的是每次生成对象的时候,都有闲置的对象直接使用。
3、对象的component:尽量在第一次预加载或者使用时,就将component加上,目的是每次生成对象的时候,不用在addcomponent了。
4、逻辑:看具体逻辑,什么样的数据每次都一样,可以缓存,什么样的数据不行,必须每次初始化呢? 比如子弹,如果要优化到极致,应该是每次发射的时候,速度,威力等等参数尽量不操作,而至改变初始位置。当然这一步可能花费大量开发时间而效果甚微。
好啦,然后我们把对象分个类,什么对象只用加载资源,什么对象要加载多少个,分清层次,就能实现最小的内存节约最多的时间啦。
下次有机会在探讨下缓存池的实现和一些实际问题把。