[前言]

去年自己写了一个Vulkan内存分配器,没用vma,整体学习的UE的vulkan内存分配器,就此记录一下

这里有一篇很好的文章,韦易笑大佬写的《如何设计内存池》:
https://www.zhihu.com/question/25527491/answer/56571062

[内存分配器整体架构]

整体架构学习的是UE 4.27中的vulkan内存分配器

图片

[内存分配]

整体是一个FreeList内存池管理,并做一些优化,虽然不是2次幂,但是也有伙伴算法的思想,先分出固定大小Page,减少外部碎片,再在Page中使用slab分配小内存,减少内部碎片,并作内存回收合并碎片。

首先按照memory_properties的memoryType分出一堆的Heap,每个Page里具体分出一些Bucket,小内存一般分配2 << 20,大内存分配64llu << 20llu,页大小小的是8llu << 20,大的是128 * 1024 * 1024

图片

Heap里每个Bucket又能分配出很多固定大小的(Bucket允许的大小)的Page,每个Page里使用FreeList管理分配出给用户的内存

Vector<VK_MemoryResourceFragmentAllocator*> active_pages[MAX_BUCKETS];
Vector<VK_Section> free_list;

FreeList的管理采用另一个结构:(数组模拟链表,内存连续,快速操作,但是只能使用链表尾部)原本的连续内存是只有一个Section,但是当回收内存的时候,产生了很多的内存碎片的时候,内存不连续那么FreeList数量就会变多

Vector<VK_AllocationInternalInfo> internal_data;
Int internal_free_listnode_index=-1;

AllocationInternalInfo中主要存储了当前这个Section的大小和在Page中的偏移:

图片

给到用户的是一个Allocation:

图片

在具体分配内存时先按照设定的VulkanAllocationFlags找到具体的Heap,然后找到合适的Bucket拿到Page,如果Page还能够分配出内存则使用这个Page否则会新建一个

图片

图片

图片

[内存回收]

内存回收的起始是Allocation放回FreeList,然后对FreeList做碎片整理。碎片整理后面再说。
FreeList整理完成之后,如果整个Page里没有了分配给用户的Allocation则会对Page进行释放,

图片

图片

图片

Page的释放很有可能不是及时的,因为Page是实际调用了vkAllocateMemory和vkFreeMemory的,所以Page的回收是先临时放入一个cache中的

图片

以便在快速的再次分配时直接拿来用:

图片

然后在具体的MemoryBlock超时或者是内存不足是会进行物理内存的回收(通常在一帧结束的时候执行这个过程):

图片

[内存碎片整理]

主要有以下几个函数:

图片

全部都是发生在回收Allocation的时候:

首先是:碎片合并InsertAndTryToMerge:能够和前一个合并,后一个合并,连续合并:

图片

然后是AppendAndTryToMerge,直接往最后合并:

图片

最后是合并连续碎片MergeConsecutiveRanges:

图片