STL第二级配置器__default_alloc_template

我们知道,SGI STL的第一级配置器是直接使用malloc(), free(), realloc()并配合类似C++ new-handler机制实现的。第二级配置器的工作机制要根据区块的大小是否大于128bytes来采取不同的策略:

申请的内存是否大于128bytes?
–>使用第一级配置器–>以malloc(), free(), realloc()等C函数执行操作,并实现类似C++ new-handler的机制
–>使用memory pool–>???

1.memory pool的工作原理是什么?

我们已经知道了,当区块小于128bytes时,就会以内存池(memory pool)来管理,此法也称为次层配置(sub-allocation),其工作原理是:
(1)每次配置一大块内存,并维护对应的自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-list中拨出。
(2)配置器除了负责配置,还负责回收:如果客端释放小额区块,就由配置器回收到free-list中。
(3)SGI第二级配置器会主动将任何小额区块的内存需求量上调到8的倍数(例如客户端申请30bytes,就自动调整为32bytes)。
(4)同时,SGI第二级配置器维护16个free-list,各自管理大小分别为8,16,24…128bytes的小额区块。

2.我们该如何去维护一个自由链表呢?

我们知道,一方面,自由链表中有些区块已经分配给了客端使用,所以free-list不需要再指向它们;另一方面,为了维护free-list,每个节点还需要额外的指针指向下一个节点。
那么问题来了,如果每个节点有两个指针?这不就造成了额外负担吗?本来我们设计STL容器就是用来保存对象的,这倒好,对象还没保存之前,自己已经占据了额外的内存空间了。那么,有方法解决吗?有的!

(1)再这之前我们先来了解另一个概念——union(联合体/共用体),对union已经熟悉的同学可以跳过这一部分的内容:
(a)共用体是一种特殊的类,也是一种构造类型的数据结构。
(b)共用体表示几个变量共用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。
(c)所有的共用体成员共用一个空间,并且同一时间只能储存其中一个成员变量的值。例如如下:

一个union 只配置一个足够大的空间以来容纳最大长度的数据成员,以上例而言,最大长度是double 类型,所以StateMachine 的空间大小就是double 数据类型的大小。在C++里,union 的成员默认属性页为public。union 主要用来压缩空间。如果一些数据不可能在同一时间同时被用到,则可以使用union。

(2)了解了union之后,我们可以借助union的帮助,先来观察一下free-list节点的结构:

以及free-list的实现技巧图:STL源码剖析图2-4

在union obj中,定义了两个字段,再结合上图来分析:
从第一个字段看,obj可以看做一个指针,指向链表中的下一个节点;
从第二个字段看,obj可以也看做一个指针,不过此时是指向实际的内存区。

3.第二级配置器的部分实现内容

到这里,我们已经基本了解了第二级配置器中free-list的工作原理了。最后附上第二级配置器的部分实现内容源码:

4.空间配置函数allocate()

我们知道第二级配置器拥有配置器的标准接口函数allocate()。此函数首先判断区块的大小?
大于128bytes–>调用第一级配置器。
小于128bytes–>就检查对应的free list。(如果没有可用区块,就将区块上调至8倍数的边界,然后调用refill(),为free list重新填充空间。
下面是allocate()代码:

NOTE:每次都是从对应的free list的头部取出可用的内存块。然后对free list进行调整,使上一步拨出的内存的下一个节点变为头结点。

区块自free list调出的操作,如图所示:stl源码剖析图2-5

5. 空间释放函数deallocate()

同样,作为第二级配置器拥有配置器标准接口函数deallocate()。该函数首先判断区块大小?
大于128bytes就调用第一级配置器。
小于128bytes就找出对应的free list,将区块回收。

NOTE:通过调整free list链表将区块放入free list的头部。

区块回收纳入free list的操作,如图所示:stl源码剖析图2-6

6.重新填充free lists

我们知道,(1)当发现free list中没有可用区块时,就会调用refill()为free list重新填充空间;(2)新的空间将取自内存池(经由chunk_alloc()完成);(3)缺省取得20个新节点(区块),但万一内存池空间不足,获得的节点数可能小于20。
下面是源代码:

6.内存池(memory pool)

唔…从文章开头就提到了memory pool,现在终于轮到这个大boss上场了。
首先,我们要知道从内存池中取空间给free list使用,是chunk_alloc()在工作,它是怎么工作的呢?
我们先来分析chunk_alloc()的工作机制:
chunk_alloc()函数以end_free – start_free来判断内存池的“水量”(哈哈,很形象的比喻)。
(1)当向内存池申请空间时,需要判断该内存池中还有多少“水量”?
(a)水量充足———>直接调出20个区块返回给free list(缺省是20)
(b)1≤水量<20——>拨出这不足20的区块的空间出去
(c)水量=0———–>调用malloc()从heap中配置内存(新水量的大小为需求量的两倍)
(2)万一连system heap空间都不够了,malloc()注水失败怎么办呢?此时,chunk_alloc()会寻找有无“尚有未用区块,且区块足够大”的free list?
(a)有—–>找到了就挖出一块并交出
(b)无—–>调用第一级配置器
(3)我们知道第一级配置器其实也是使用malloc()来配置内存的,但它有out-of-memory处理机制(类似new handler机制),或许有机会释放其他的内存拿来此处使用。
(4)如果第一级配置器的malloc()也失败了,就发出bad_alloc异常。

说了这么多,我们还是来看看SGI STL中的源码吧:

下图显示内存池实际操作结果:stl源码剖析图2-7

小结:
SGI STL的一级、二级空间配置器的内容就结束了。回想一下前面提到的那个提供配置器标准接口的simple_alloc,SGI容器通常以这种方式来使用配置器:

其中第二个template参数所接受的缺省参数alloc,可以是第一级配置器,也可以是第二级配置器。不过,SGI STL已经把它设为第二级配置器。

参考:STL源码剖析

发表评论

电子邮件地址不会被公开。 必填项已用*标注