第四讲 loki::allocator
还记得我们第二讲中标准库的allocator中,在回收内存的时候并没有调用free将内存归还给操作系统,而是自己管理。而Loki Lib的allocator就解决了这个问题,我们现在来看看它是怎么实现的。
一、设计结构
我们先来看它的设计结构,最外层是SmallObjectAllocator,然后它有一组FixedAllocator,而FixedAllocator又包含了一组Chunk,我们从下往上来看,
Chunk有一个指针pData指向一块内存空间;然后是firstAvailablBlock,它表示下一块优先级最高的可分配的内存块的下标(注意哦,是下标,从0开始的,它不对应下图所说的流水号);然后是blocksAvailbale,它表示剩余可分配的内存。
然后向上看FixedAllocator,它还有2根指向Chunk的指针,可以推测它应该是用来管理的。
然后再向上看SmallObjectAllocator,它同样也有2根指针,也可以推测它也是应该是用来管理的。
1.Chunk设计![image-20220828144454198](https://i-blog.csdnimg.cn/blog_migrate/0cd19ae5e55e5419e15c5f01edd365cc.png)
我们先来看Chunk中的函数实现,首先来看init(),发现它调用了array new来分配一块内存空间,然后调用reset,来设置Chunk的状态,可使用的内存块为blocks也就全部,下一块要使用的内存块的下标为0(图的顺序有点问题),我们来看下面的for loop,发现它并不是使用链表将内存一块一块串联起来,而是使用了流水号将他们标记,它表示的是它的下一块内存的下标。然后有一个Release(),发现它调用了delete[],来释放内存,并交给操作系统,解决了我们的问题。
我们主要来看它的分配与释放动作。
再来看Chunk的Allocate()设计,现在假设内存的状态是左边情况,然后调用一次Allocate()后,成为右边状态。,我们来看它是如何变化的。
根据firstAvailableBolck,可知下一块要分配的内存是下标为4的(0,1,2,3,4),计算后发现是标号为3的那一块内存,所以就将这一块内存分配出去,然后根据它的标号,找到下标为3的那一块内存(0,1,2,3),计算后发现是标号为2的那一块内存,修改firstAvailableBolck的值,表示下一次分配的内存就是它了。然后修改blocksAvailable的值。
可以根据图中灰色小箭头走一遍理解,从标号3开始,指向下标为3的标号2,然后指向下标为2的标号1,然后指向下标为1的标号5…
然后我们再来看看Chunk的Deallocate(),首先根据传进来的指针p,减去头,再除以大小,可以计算出它的下标位置,将它回收,然后修改它的标号为当前firstAvailableBlock的值,然后将firstAvailableBlock的值设置为它的下标位置,表示下一次会先将它分配出去,然后修改blocksAvailable的值。
2.FixedAllocator设计
然后向上来看Chunk的上一层FixedAllocator的Allocate()是如何实现的。
我们知道FixedAllocator中有两根指针,allocChunk与deallocChunk:
allocChunk,表示上一次分配内存的chunk,怎么理解呢,就是上一次内存分配过之后,你还有没有剩余的内存来满足下一次内存分配呢。
deallocChunk,表示上一次回收内存的chunk,避免回收的时候,重复回收(因为计算机中数据有内聚性,就是会集合在一块区域)。
我们来看源代码,如果是首次分配内存或当前Chunk的可使用的内存为0,就开始遍历整组Chunk,寻找可以使用的内存,如果找到了可使用的内存,就取出这个Chunk,然后break出去,调用这个Chunk的Allocate()方法;如果没有找到,表示这组Chunk的内存用完了,就重新push进去一个Chunk,然后调用init(),设置这个Chunk的状态,然后设置allocChunk与deallocChunk,然后break出去调用这个Chunk的Allocate()方法。
你可能会疑惑,为什么要设置deallocChunk呢,因为容器的push_back可能会导致容器内存空间的重新分配,为了避免deallocChunk指向垃圾值,只能将它指向容器头。
然后我们来看Deallocate(),它会先去找传进来的指针p位于那一块Chunk中,找到后标记起来,然后回收。
我们先看如何查找,首先计算了一个Chunk的大小,然后定义了4个指针,分别表示上一次回收内存的chunk,上一次回收内存的chunk的下一个chunk,chunk组的头,chunk组的尾。我们来看它是如何设计的明白了这四个变量的用意,如侯捷老师所说兵分两路,从lo开始向上找,从hi开始向下找,直到查找完整个chunk组。那它是怎么找的呢,两边夹的方法,就可以知道是在哪一块chunk中,找到了就返回chunk(只要是前面allocate分配的,它是没有可能找不到的),然后调用DoDeallocate()回收。
现在来看DoDeallocate(),首先调用chunk的Deallocate()回收,然后就是判断该chunk是否是全回收,如果全回收了同样不着急先归还给操作系统,而是直到有另一块全回收的chunk才会归还,和前面SBH的设计一样。并且这个版本的延缓回收操作有一个bug,有可能不会将chunk归还给操作系统,这里就不分析了。
二、总结
这里说一下上面的两条:
使用「以array取代 list,以 index取代pointer」的特殊实现手法.
这个lib中的Chunk是使用array与index替代了前面我们所熟悉的链表与指针的设计,这种设计技巧我们也可以学习
这是个allocator,用来分配大量小块不带cookie的 memory blocks,它的最佳客户的容器,但它本身却使用vector , What happens ?蛋生鸡?鸡生蛋?
你是不是会想到,它本身是被容器使用的,而现在它却使用了容器,这里面不会出现什么问题么?
注意,这里其实并不存在所谓 “蛋生鸡?鸡生蛋?”的问题,这个allocator使用了vector,而这个容器使用的是默认标准库的allocator ,为了避免迷糊,更好的设计是不使用标准器容器。
最后:
欢迎指正不足或错误的地方。如果文章对你有所帮助,欢迎点赞支持。欢迎转载。