UnityGC垃圾回收及优化

算是这个帖子和一些其他别的帖子的归纳总结
https://www.cnblogs.com/dudu580231/p/7160547.html
这个是析构函数
https://blog.csdn.net/leonwei/article/details/52471026

资源创建

说道回收肯定提到创建,有创建才有回收,Unity一个是Resources.Load 还有个是通过AB包。
区别的话
1.AB包在StreamingAsset目录下不会被打包,可以从服务器下载文件到StreamingAsset不进行打包,就是热更,而Resources会被打包时自动给打进exe,更改资源就需要重新打包。
2.官方不推荐用Resources,官方描述


image.png

你加载资源是遍历检查如果东西越多遍历越慢
AB包可以分包加载从而不用去遍历,所以加载可能快一点。
3.AB包可以自己选择压缩算法和打包加载策略(按场景、关卡、或者整体复用)

加载步骤

就按AB包来说(其实上Resources和AB差不多)
1.CreateFromMemory(byte[]),这个byte[]可以来自文件读取的缓冲,www的下载或者其他可能的方式。(CreateFromFile忽略因为只能用在standlone程序)
2.把AB镜像数据块读取到一个内存(没有成Asset)
3.AssetBundle.Load(同Resources.Load)从AB内存镜像创建一个Asset以及需要的内存用于存放
4.加载各种其他Assets(Gameobject transform mesh texture material shader script)(我觉得这个应该是提前加载的不然预制体加载了 引用就丢失了)
5.Instance克隆实例化,相当于Clone加上引用结合,就GameObject是Clone的,其他mesh / texture / material / shader 全是引用。

释放

所以Destory时我们只是移除了Clone对象,不会释放他的引用( mesh texture material shader script),因为它不知道这些引用是否被别的物体所引用。
等到这些引用都没有用的时候就通过 Resources.UnloadUnusedAssets释放,
AssetBundle.Unload(false)也不行,AssetBundle.Unload(true)可以但不安全,除非你很清楚没有任何 对象在用这些Assets了。
这个false就是AB包释放内存镜像,不包含Load创建的Asset内存对象
true相当于释放完了,然后别的引用这个的物体都会丢失引用信息

内存占用大怎么办

之后就按AB包说的算,因为Resources我们无法控制,可以AB包加载Load完之后用AssetBundle.Unload(false)释放镜像资源这样的骚操作,不过最好是不再加载的资源把,之后再Load还要加载

1.先建立一个AssetBundle,无论是从www还是文件还是memory
2.用AssetBundle.load加载需要的asset
3.用完后立即AssetBundle.Unload(false),关闭AssetBundle但不摧毁创建的对象和引用
销毁时:
1.对Instantiate的对象进行Destroy
2.在合适的地方调用Resources.UnloadUnusedAssets,释放已经没有引用的Asset.
3.如果需要立即释放加上GC.Collect(),因为GC是他会自动调用释放可能不及时,但是不要调用太频繁很消耗性能,里面会调用析构函数,然后再销毁。
这样可以保证内存始终被及时释放

只要你Unload过的AssetBundle,那些创建的对象和引用都会在LoadLevel时被自动释放。

加载方式

一是静态引用,建一个public的变量,在Inspector里把prefab拉上去,用的时候instantiate
二是Resource.Load,Load以后instantiate
三是AssetBundle.Load,Load以后instantiate
三种方式有细 节差异,前两种方式,引用对象texture是在instantiate时加载,而assetBundle.Load会把perfab的全部assets 都加载,instantiate时只是生成Clone。所以前两种方式,除非你提前加载相关引用对象,否则第一次instantiate时会包含加载引用 assets的操作,所以拖好的去克隆会导致第一次加载时间变长

关于AssetBundle.Unload

Object obj = Resources.Load("MyPrefab");
GameObject instance = Instantiate(obj) as GameObject;
.........
Destroy(instance);
创建随后销毁了一个Prefab实例,这时候 MyPrefab已经没有被实际的物体引用了,但如果这时:
Resources.UnloadUnusedAssets();
内存并没有被释放,原因:MyPrefab还被这个变量obj所引用
这时候:
obj = null;
Resources.UnloadUnusedAssets();
这样才能真正释放Assets对象 必须保证没有引用才可以
这个比较详细
http://www.newhappy.com.cn/?p=1058

Unity中的内存种类

程序代码、托管堆(Managed Heap)以及本机堆(Native Heap)。
1.程序代码
就是unity引擎代码、使用的库(.Net)、自己的代码

这个优化比较难优化,主要就是减少打包时的引用库(剥离库)
在Player Setting这里


image.png

Pro版的Unity可以选择“Use micro mscorlib”使用最小的库
前提是你的项目用不到这些库


image.png

2.托管堆
是被Mono使用的一部分内存,相当于基本类库。
托管堆用来存放类的实例(比如用new生成的列表,实例中的各种声明的变量等)
“托管”的意思是Mono“应该”自动地改变堆的大小来适应你所需要的内存,并且定时地使用垃圾回收(Garbage Collect)来释放已经不需要的内存。关键在于,有时候你会忘记清除对已经不需要再使用的内存的引用,从而导致Mono认为这块内存一直有用,而无法回收。
频繁使用创建销毁可以使用对象池,激活失活代替创建销毁
https://www.gameres.com/806194.html 这个文章还是蛮不错的
3.本机堆
就是Unity引擎申请操作的地方(贴图,音效,关卡数据)
Unity使用了自己的一套内存管理机制来使这块内存具有和托管堆类似的功能
就是上面说的内存占用过大优化,不过再提一点虽然GC会在场景切换调用,但是你的static的单例(singleton)不会被销毁,记得用完及时释放。还有DontDestroyOnLoad()挂在的物体也是,尽量保存一些状态信息,不要储存大量贴图,声音占用大的东西。

这个帖子分析比较深
https://blog.csdn.net/u014635337/article/details/77996161

基本优化列举一下


image.png

基本上每次调用的临时数组可以提取出来 这样多次进入只用一个缓存接收


image.png

每帧调用时候优化
image.png

如果发生改变在执行 而不是每帧执行


image.png

list用Clear然后重新赋值而不是在new一个新的,这个是Clear源码相当于把所有里面每一个=null 长度归0,不用重新new一个list
image.png

还有之前说的频繁创建销毁用对象池

字符串类
 c#中的字符串是不可变更的,也就是说其内部的值在创建后是不可被变更的。每次在对字符串进行操作的时候(例如运用字符串的“加”操作),unity会新建一个字符串用来存储新的字符串,使得旧的字符串被废弃,这样就会造成内存垃圾。
1.如果一个字符串被多次利用,我们可以创建并缓存该字符串。
2.例如如果在Text组件中,有一部分字符串需要经常改变,但是其他部分不会,则我们可以将其分为两个部分的组件。
3.如果我们需要实时的创建字符串,用StringBuilder专为不需要进行内存分配而设计,从而减少字符串产生的内存垃圾。
4.移除游戏中Debug.Log()

比如我们显示时间


image.png

可以分开


image.png

这样写相当于每次都去myMesh获取这个数组再获取长度


image.png

这样就只用获取一次了 不过这个length是值类型 如果长度发生变化,他是不会变的 所以看情况提取出来


image.png

还有个gameObject.tag==playerTag 相当于多生成一份tag进行对比
改为这个gameObject.CompareTag(playerTag)
比如我们可以用Input.GetTouch()和Input.touchCount()来代替Input.touches
所以为什么我们不直接点出来参数而是封装一个方法进行return
Physics.SphereCastNonAlloc()来代替Physics.SphereCastAll()。


image.png

所以之前的碰撞都可以改为这个


image.png

还有装箱拆箱操作
贴一些别人的介绍


image.png

image.png

image.png

拆箱比装箱消耗性能少的多 因为装箱值类型不能传递就new了个引用类型再赋值 尽量避免 有些插件什么的都很难避免装箱拆箱,这也是我们用List而不用ArrayList的原因,泛型在一定程度上可以优化这个问题

这个也是装箱 尽量用null


image.png
image.png
image.png

不过现在foreach都是var 应该没什么问题 还有一个相对于var静态类语言的,dynamic是动态类语言,可以简化反射(反射优化过的好像性能比不过)

image.png

https://blog.csdn.net/chy555chy/article/details/80353043
这个说的挺详细的

还有就是struct有引用类型变量GC需要检查整个struct


image.png

这个就是保存返回一个ID标记而不是整个类


image.png

匿名函数和闭包

image.png

这个是详细的
https://www.cnblogs.com/jiejie_peng/p/3701070.html
就是我之前btn写的注册还有,外部往里传值出的问题,不过倒是可读性挺高,代码简洁的话不用再另外创建一个类跳来跳去。
但是每次都会创建新对象
这个匿名函数编译后竟然包含装箱
image.png

但是为了简便该用时候还是要用 但是不能用在调用频繁的函数里 ,如:Update, LateUpdate等
这个点相当于每次new了个新数组,不如一个Get方法return返回
image.png

然后还有些源生优化


image.png

材质操作


image.png

声音操作


image.png

然后是最重要的纹理操作


image.png

博主给出了个AB包资源系统的书的链接
http://www.newhappy.com.cn/wp-content/uploads/2018/10/UnityResourceManager.pdf
这个是纹理压缩的帖子
http://www.newhappy.com.cn/?p=907
里面有个霍夫曼编码
https://www.toutiao.com/i6658771527455998472/这个说明的比较形象
这句是要点

image.png

你可能感兴趣的:(UnityGC垃圾回收及优化)