- 我们都知道,Unity底层是C++实现的,游戏引擎都不会直接通过malloc与new直接分配内存,至少都要在其上封装一层来做内存管理、统计与检查等工作
- Unity引擎中,C++层会根据内存用途不同,抽象成不同的allocator进行内存分配,通过不同的Memory Lable标签与OwnerShape的设置进行标记与追踪,通过MemoryManager进行整体的管理
- 这几块都在Unity源码中,购买过源码的可以在源码中看,没有购买过的,只有Allocator可以在Unity2021以上版本中的ProjectSetting-Memory Setting中窥得一些端倪
- Unity Native层会根据分配算法的不同以及用途的不同定义非常多类型的内存分配器。今天,我们只需要关注MemorySetting中,我们可以自定义并且比较典型的几类
分配器Allocator的分类
- Main Allocators:绝大多数内存分配使用的分配器,包括主线程、渲染资源相关、文件cache、typetree等不同用途下的分配器
- Fast Per Thread Temporary Allocators:线程上使用的临时分配器,包括各工作线程的栈分配器,比如音乐、渲染、预加载、烘焙等工作线程上的分配器
- Fast Thread Shared Temporary Allocators:线程间共享的临时分配器
这四种大的分类与其中的小类,都是用途抽象意义上的分配器,在Unity引擎代码中并没有一一对应的分配器的类,而在Unity代码中则是根据分配算法分类的分配器
包括Unity默认分配器、桶分配器、动态堆分配器、双线程分配器、线程本地存储分配器、栈分配器、线程安全线性分配器以及用于各个平台特性的分配器与调试分配器太多太多
它们与按用途分配的分配器是一对多的关系。也就是按用途分类的分配器底层都是以按算法分类的分配器实现的
它们只是底层分配器抽象的一层皮,而MemorySetting自定义设置中既是把这层皮暴露到编辑器中了。我们需要了解它们具体对应关系才能真正了解如何下手去自定义内存分配器
以下图表大致描述了底层按算法分类的几大类分配器,与按用途划分的分配器的关系
主要的底层分配器
桶分配器
- 在Unity Player下,默认的粒度大小为16个字节,用于分配16、32、48、64等等字节的内存
- 该分配器默认分配保留1个内存块Block,每个块会被划分为16kb的子段,并且这个大小不可配置。如果需要调整Block块,则Block块只能增长,并且需要是固定16kb大小的整数倍
- 这个分配器的粒度大小与Bucket的个数是需要根据游戏内情况来配置的,默认粒度是16个字节,默认的Bucket是8个,这样在Release版Player下,1个16kb子段下最多可以容纳1024个Bucket;而如果将Bucket的粒度调整为64个字节,则1个子段只能容下256个了
动态堆分配器
- TLSF算法:你可以理解为——它是两层链表管理的内存块,第一层以2的幂次方划分,当第一层的内存块分配有剩余时,引入第二层链表,将第一层中使用的内存块剩余部分用更精细的粒度进行划分。在保证分配效率的同时提高内存利用率
双线程分配器
两个线程上使用的分配器
TLS Stack Allocator
- 如果发生溢出回退情况,则会回退到Thread Safe Linear Allocator
Thread Safe Linear Allocator
- 如果发生了溢出,需要判断该溢出是否发生在资源加载期,还是正常的帧渲染期
- 如果加载期溢出,帧渲染期平稳,我们可以不用理会;如果正常的帧渲染期溢出数量增加,我们需要调高该Allocator的Block Size大小
- 不过如果你有足够的内存预算,无论是在加载期还是在正常帧渲染期发生溢出的情况下你都可以调高
- Unity2021版本上已经有文档可以学习了
- 2021以下版本想要自定义内存分配器,只能通过修改源码的方式进行了