Unity内存

2021-10-09  本文已影响0人  不正经的搬砖工

1、Unity内存按照分配方式分为:

(1)Native Memory(本地内存)

(2)Managed Memory(托管内存)

Editor和Runtime内存管理是不同的,Editor下打开工程就会将所有资源加载进内存,而Runtime是我们调用相应的Load接口时才会加载进内存。

2、Unity按照管理者分为:

(1)引擎管理内存

(2)用户管理内存

3、Unity Native Memory管理

图3-1 Native-Memory分配流程

Unity通过重载C++的new/malloc内存分配操作符实现自己的内存管理。

3.1  Allocator与memory label

memory label区分内存类型,每一种内存类型都会对应一个Allocator,每个Allocator有各自的分配策略,每一个Allocator会跟踪一个内存池。当需要申请内存时,就会找到对应类型的Allocator进行分配。

3.2  NewAsRoot

调用NewAsRoot时会分配所有

3.3 Stack Allocator

Stack Allocator用于分配局部内存,Stack Allocator的特点就是快、小、临时。

当Stack Allocator分配内存时,一次分配一个大的Heap Block块,然后通过栈的方式管理这块Block的分配。实际分配内存时会包含一个Header和User Data,Header记录当前使用状态、User Data大小等信息。使用一个栈顶指针管理内存分配,每次分配只在栈顶顶端新分配一块内存,然后修改栈顶指针。回收时如果回收的是非栈顶内存只需要修改Header中使用状态为Deleted;如果回收的是栈顶指针指向的内存,修改标记状态同时,栈顶指针向上回弹,回弹后会查看当前指向的内存是否已标记为Deleted,如果是则继续回弹,直到栈顶指针指向一个未标记为Deleted的内存。

图3-2 Stack-Allocator示意图

当一个Block块无法分配后,会再申请一块Block。但不可能无限分配,Stack Allocator有大小限制;

Editor:16MB Main Thread,256KB workers;Runtime:1MB Main thread,64KB workers

如果我们需求内存过大导致Stack Allocator大小不够,Unity会执行Fallback机制,会使用Dynamic Heap Allocator分配内存。Dynamic Heap Allocator分配策略相对Stack Allocator复杂很多,所以耗时比较大,出现这种情况可能导致卡顿。出现这种情况时Unity会给出一个MemoryManager.FallbackAllocation的提示。所以可以分帧加载一个比较大或多的资源。

3.4 影响Native内存大小因素

(1)Scene:场景中GameObject越多占用内存越大

(2)Audio

a、DSP Buffer Size(PlayerSettings->Audio)

声音数据缓冲,太大可能导致声音延时,太小导致发送给CPU太频繁

b、Force to mono:强制改成单声道,内存相对双声道减半

c、Format

d、Compression Format

(3)Code Size

模板泛型滥用

(4)AssetBundle

a、TypeTree:保证不同版本序列化正确

如果确保Bundle版本和Unity版本一致,打Bundle时可以通过设置BuildAssetBundleOptions.DisableWriteTypeTree关闭TypeTree,减小包体、省内存、并且可以提升打包速度。

b、LZ4(trunk based)/LZMA

c、设置合适的AB大小和数量

(5)Resources文件夹

打包时会生成R-B Tree用于Resources检索

(6)Texture

a、upload buffer(Project Settings->Quality->Async Upload Buffer Size)

b、read/write

c、Mip Maps

(5)Mesh

a、read/write

b、compression

(6)Assets

4、Unity Managed Memory

图4-1 Managed Memory分配机制

4.1 

Unity使用Boehm回收器,Boehm是保守式回收器。

由于分代式垃圾回收移动内存、压缩等开销较大,不适合移动平台,所以Unity选择使用Boehm回收。

Unity不论使用Mono还是IL2CPP都使用Boehm回收器

Unity嵌入早期版本的Mono(具体原理可参考Mono自动内存管理分析),采用非分代、非压缩的保守式GC,托管内存只增不减。

IL2CPP由Unity自己重写垃圾回收机制,是升级版的Boehm。使用IL2CPP,托管内存可以降低,内存返还的条件就是当GC6次,某一个Block都是闲置时,就会将这个Block返还给系统。

图4-2 回收机制展示

4.2 Managed Memory最佳实践

(1)使用Destroy,别用Null;

(2)Class VS Struct;

(3)缓存池;

(4)Closures and anonymous methods(闭包和匿名函数);

(5)Coroutines(协程);

(6)Configuration(配置表);

(7)Singleton

(8)先分配大内存再分配小内存减少碎片化

4.3 Incremental GC

Unity2019.1引入Incremental GC(渐进式GC),在使用Boehm回收器的基础上以增量模式运行,将垃圾收集拆分到多帧进行。

大多数情况下,Incremental GC可以减少垃圾收集尖峰问题。但某些情况下,使用Incremental GC可能会产生其它问题:

(1)Incremental GC是拆分的标记阶段,当对象引用改变时,必须在下一次迭代中再次扫描这些对象,如果在标记阶段不断有引用关系的变化,可能导致标记遍历永远不能完成,这种情况下,垃圾收集会退回到进行完整的非增量收集。

(2)另外,Incremental GC时,引用发生改变,Unity就需要设置写屏障通知垃圾收集,会增加开销,对代码产生性能影响。

5、Native和Managed关系

图5-1 Native和Manager Object关系(视频地址:https://www.bilibili.com/video/BV1J64y187TW?p=3)

如图5-1,上半部分是Native Object创建过程,下半部分是Managed Object创建过程。实际上不论我们是创建一个native object还是managed object都可能同时创建一个关联的managed/native object。

比如加载一个Texture会创建一个native object实例,同时也会创建一个托管的Texture实例。

Texture _texture = Resources.Load<Texture>("Unity");

我们将_texture置为null,然后调用GC.Collect();此时在Profiler中查看_texture实例并没有被回收,而当我们调用Resources.UnloadUnusedAssets()后_texture销毁了。

上一篇 下一篇

猜你喜欢

热点阅读