SGI STL空间配置器和内存池

      最近在看侯捷老师的《STL源码剖析》,非常感叹其中空间配置器实现的巧妙和细致,对效率真正是锱铢必较。

  一般我们所习惯的内存配置和释放是通过new和delete来完成的,而new运算包含了两个阶段:1.调用::operator new配置内存 2.调用构造函数 Foo() 构造对象。delete运算也包含两个阶段:1.调用析构函数 ~Foo() 将对象析构 2.调用::operator delete释放内存。

1 class Foo { … };
2 Foo *pf = new Foo;
3 delete pf;

  而SGI STL为了提高效率则是把二者分开的,对象的构造的析构由 construct() 和 destroy() 完成,内存的配置则是由std::alloc完成,alloc是SGI STL中的默认空间配置器,它是具有次层配置能力的,分为一级配置器和二级配置器,一级配置器直接由 malloc() 和 free() 这两个 C 函数完成,二级配置器就复杂了,它根据不同的情况而采取不同的策略,当申请的内存区块大于128bytes时,就移交给一级配置器处理,小于 128bytes则由二级配置器采用复杂的内存池( memery pool )完成,这主要是为了减少内存碎片的产生,而且还能降低额外负担,什么负担呢?我们每申请一块内存,都要向系统上缴一部分“税”,当然这些税取之于民也用 之于民,主要是用来记录内存的相关信息,如大小之类的,所以当内存越小时这种额外的负担就越大。

  一级配置器会在内存不足 ( out-of-memory) 时,调用oom_malloc(),执行内存不足处理例程,__malloc_alloc_oom_handler() = 0; 不断尝试释放其它内存,请注意该理例程是要用户自己设计并且要自己设定的!否则系统会毫不客气的调用 _THROW_BAD_ALLOC,抛出 bad_alloc 异常信息,或者直接 exit(1) 来终止程序!其中__THROW_BAD_ALLOC是一个宏:

复制代码
1 #if 0
2 # include <new>
3 # define __THROW_BAD_ALLOC throw bad_alloc
4 #elif !define( __THROW_BAD_ALLOC)
5 # include <iostream.h>
6 #define __THROW_BAD_ALLOC cerr<<" out of memory "<<endl;exit(1)
7 #endif
复制代码

  然后我们看二级配置器,它是以内存池 ( memory pool ) 来管理的:它由16个自由链表构成,每次配置一大块内存,并维护对应之自由链表 ( free-lists ),下次若有相同大小的内存请求,就直接从free-lists 中拨出这块内存,同时,如果客端释放小额区块时,刚把它回收到对应大小的自由链表 ( free-lists )中。

  这16个肩负重任的 free-lists 分别管理大小为 8,16,24,32,40,56,64,72,80,88,96,104,112,120,128 bytes 大小的小额区块,所以当申请小额内存时,内存的需求量会被上调至 8 的倍数,以便于管理。接下来内存池就要登场了……

  首先由空间配置函数 allocate() 进行空间的配置,如果能够在 free-lists 中找到对应大小的区块,就把对应的区块拨出,返回。否则就要调用 refill() 对 free-lists 进行填充,新的空间来自内存池,由 chunk_alloc() 完成。缺省是取得20个新区块,但如果内存池空间不足数量会小于20。取得的新区块,把第一块 return ,剩余的则维护到free-list中。

  我们看下 chunk_alloc() 是怎么工作的:

  从内存池是取出新空间可能发生三种情况

  1. 内存池剩余量完全满足需求量

  2. 内存池剩余量不能完全满足需求量,但足够供应  >= 1 个 区块

  3. 内存池剩余量一个区块都不能供应

复制代码
 1  chunk_alloc(size, nobjs)
2 {
3 ……
4
5 if(bytes_left >= total_bytes) // 1.剩余量大于需求量
6 {……}

7 else if(bytes_left >= size) //2.能供应至少一个区块
8 {……}

9 else{
10 …… //3.一个都不能供应
11 }

12 }
复制代码

     首先我们看第一种情况:内存池剩余量完全满足需求量

  这种情况是最好的情况,直接返回所需的区块就可以了

  第二种情况: 内存池剩余量不能完全满足需求量,但足够供应  >= 1 个 区块

  这种情况也好办,我们只要计算出实际能够供应的区块的个数,然后返回这些区块就可以了

  剩下最后一种情况:内存池剩余量一个区块都不能供应

  这种情况比较麻烦了,我们首先要进行 “善后” , 把剩余的这些残余的空间回收到 free-lists 中,然后才申请分配新的空间。申请新空间的大小为所需空间的2倍,再加上一个随着配置次数增加而增加的附加量。内存池向堆空间索要内存,用 malloc(), 如果成功,则调整内存池的起点和终点,递归调用 chunk_alloc() 就行了,否则如果分配内存失败,就要想法从其 它地方找点内存来用了,没错!就是 free-lists,向那些较大,且有剩余区块没用的free-list索要内存,如果找到了,就挖一块返回,调用chunk_alloc() 返回区块,否则就没办法了,只能调用一级配置器了,因为那里有out-of-memory处理例程,兴许还能找出点内存,如果能找到,那最好了,调用 chunk_alloc()把区块返回给refill(),返回结果并调整free-lists,否则,只能抛出异常了……

posted @ 2012-08-14 20:51  kanego  阅读(1344)  评论(3编辑  收藏  举报