在项目开发中,我们可能会在函数中用泛型来设置参数,这样的好处是可以方便的用一个函数解决传递参数的问题。
比如我们有计时器,计时器传递参数让回调执行。
就是因为灵活性,可能会导致我们产生很多不必要的装箱拆箱操作,而且如果在update执行的话,gc就会更严重。
装拆箱
首先解释下装箱拆箱:
装箱 将值类型转换成引用类型,
拆箱 将引用类型转换成值类型。
值类型是在他自己的内存分配中存储数据,通常分配在我们的线程堆栈,不包含指向实例的指针。
引用类型会包含指向存储数据的内存位置的指针,引用类型总是从托管堆上分配的。
而装箱是要把线程堆栈中的值类型拷贝到托管堆中为他分配的一块内存中。
而拆箱是要把托管堆中为他分配的内存中把数据重新放回到线程堆栈中。
装箱和拆箱会导致cpu消耗和内存的消耗。因为会放一份数据到托管堆,会导致最终的gc产生。我们应该避免装箱拆箱。
项目例子
在项目中比如我们把一个struct类型传到object中,这样就会产生一次装箱,因为struct是值类型。
我们用ildasm就可以看到这里产生了一次装箱,同样在TryToDressDicAllMesh就会有一次unbox的操作。我就不截图了。
解决办法:
那么我们要避免装拆箱,那么首先要做的是把sDressDic类型改为class类型,class是属于引用类型,所以转换不会出现装箱问题。
另外我们要把new操作提前,而且只赋值一次,每次都服用这个类,这样才能避免多次托管堆的开销。
结合对象池
但是我们还是很难避免在大量使用方法的时候导致的大量托管堆内存开销。这个时候可以用对象池来保存我们的操作。并且设置一个上限,加上LRU(Least recently used,最近最少使用)。上限到了需要清除,下次使用再new。我们尽量把需要托管堆操作的东西放到对象池,保证我们的mono堆不会太大。同时我们也要注意对象池不能太多,不然我们使用的资源内存就会太大。一样会导致内存溢出。所以内存的使用是一个博弈的过程。