侯捷老师 C++内存管理-第四讲 学习笔记

第四讲 loki::allocator

还记得我们第二讲中标准库的allocator中,在回收内存的时候并没有调用free将内存归还给操作系统,而是自己管理。而Loki Lib的allocator就解决了这个问题,我们现在来看看它是怎么实现的。

一、设计结构

image-20220828142955713

我们先来看它的设计结构,最外层是SmallObjectAllocator,然后它有一组FixedAllocator,而FixedAllocator又包含了一组Chunk,我们从下往上来看,

Chunk有一个指针pData指向一块内存空间;然后是firstAvailablBlock,它表示下一块优先级最高的可分配的内存块的下标(注意哦,是下标,从0开始的,它不对应下图所说的流水号);然后是blocksAvailbale,它表示剩余可分配的内存。

然后向上看FixedAllocator,它还有2根指向Chunk的指针,可以推测它应该是用来管理的。

然后再向上看SmallObjectAllocator,它同样也有2根指针,也可以推测它也是应该是用来管理的。

1.Chunk设计image-20220828144454198

我们先来看Chunk中的函数实现,首先来看init(),发现它调用了array new来分配一块内存空间,然后调用reset,来设置Chunk的状态,可使用的内存块为blocks也就全部,下一块要使用的内存块的下标为0(图的顺序有点问题),我们来看下面的for loop,发现它并不是使用链表将内存一块一块串联起来,而是使用了流水号将他们标记,它表示的是它的下一块内存的下标。然后有一个Release(),发现它调用了delete[],来释放内存,并交给操作系统,解决了我们的问题。

我们主要来看它的分配与释放动作。

image-20220828145407384

再来看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…

image-20220828150014489

然后我们再来看看Chunk的Deallocate(),首先根据传进来的指针p,减去头,再除以大小,可以计算出它的下标位置,将它回收,然后修改它的标号为当前firstAvailableBlock的值,然后将firstAvailableBlock的值设置为它的下标位置,表示下一次会先将它分配出去,然后修改blocksAvailable的值。

2.FixedAllocator设计

image-20220828152607276

然后向上来看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中,找到后标记起来,然后回收。

image-20220828152650314

我们先看如何查找,首先计算了一个Chunk的大小,然后定义了4个指针,分别表示上一次回收内存的chunk,上一次回收内存的chunk的下一个chunk,chunk组的头,chunk组的尾。我们来看它是如何设计的明白了这四个变量的用意,如侯捷老师所说兵分两路,从lo开始向上找,从hi开始向下找,直到查找完整个chunk组。那它是怎么找的呢,两边夹的方法,就可以知道是在哪一块chunk中,找到了就返回chunk(只要是前面allocate分配的,它是没有可能找不到的),然后调用DoDeallocate()回收。

image-20220828161559114

现在来看DoDeallocate(),首先调用chunk的Deallocate()回收,然后就是判断该chunk是否是全回收,如果全回收了同样不着急先归还给操作系统,而是直到有另一块全回收的chunk才会归还,和前面SBH的设计一样。并且这个版本的延缓回收操作有一个bug,有可能不会将chunk归还给操作系统,这里就不分析了。

二、总结

image-20220828162526838

这里说一下上面的两条:

  • 使用「以array取代 list,以 index取代pointer」的特殊实现手法.

    这个lib中的Chunk是使用array与index替代了前面我们所熟悉的链表与指针的设计,这种设计技巧我们也可以学习

  • 这是个allocator,用来分配大量小块不带cookie的 memory blocks,它的最佳客户的容器,但它本身却使用vector , What happens ?蛋生鸡?鸡生蛋?

    你是不是会想到,它本身是被容器使用的,而现在它却使用了容器,这里面不会出现什么问题么?
    注意,这里其实并不存在所谓 “蛋生鸡?鸡生蛋?”的问题,这个allocator使用了vector,而这个容器使用的是默认标准库的allocator ,为了避免迷糊,更好的设计是不使用标准器容器。

最后:
欢迎指正不足或错误的地方。如果文章对你有所帮助,欢迎点赞支持。欢迎转载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值