Fantacity

Stand Alone Complex

STL allocator(2)

前一篇主要分析了construct()与destory()函数,接下来分析std::alloc的两级配置器。

std::alloc概览

std::alloc负责空间的配置与释放。 首先来看一下SGI STL对于alloc的采用两级配置器的思想。
第一级配置器与第二级配置器

考虑到了小型区块所可能造成的内存破碎问题,SGI设计了两级配置器,第一级直接采用malloc()与free(),第二级则视情况不同采取不同策略:当配置区块超过128bytes时,认为区块足够大,转而调用第一级配置器,反之则从预先申请的memory pool当中整理出所需大小的内存来分配,使用完毕后归还给memory pool。

另一方面,为了符合STL的标准,SGI STL在alloc外面又包装了一个simple_alloc,主要是实现了4个函数,方便以模板类大小为单位分配和释放内存,如下图所示:
STL allocate

第一级配置器

来看一下第一级配置器__malloc_alloc_template,先上代码。
第一级配置器代码
代码的注释已经写得很详细了,这里不做过多说明了。想说明的一点是,由于alloc选择了malloc和free作为第一级配置器,所以需要自己处理oom的情况,于是它模仿c++中的set_new_handler()方法,让用户自己设置oom情况下的处理函数(set_malloc_handler函数),并在发生oom时尝试不断循环调用__malloc_alloc_oom_handler函数,申请内存……

另外为什么SGI以malloc而非::operator new来配置内存,书中给出了可能的猜测:一方面由于历史因素(当然啦),另一方面c++并未提供相应与realloc()的内存配置操作,因此SGI不能直接使用c++的set_new_handler(),必须仿真一个类似的set_new_handler()。

第二级配置器

第二级配置器多了一些机制,避免了太多小额区块造成内存的碎片。我们知道,小区块的内存所需要的额外相对于大区块内存负担是更大的,更加浪费,如下图所示。
小块内存的额外负担

SGI第二级配置器的做法是,如果区块够大,超过128bytes时,就移交第一级配置器。当区块小于128bytes时,则以内存池(memory pool)管理。配置器主要有两部分组成:free-lists 和 memory pool。其中free-lists以8为倍数包含了从8-128bytes共16个free-list,以供客户端的小额内存申请需要。为了方便管理,SGI把客户端的内存申请自动上调至8的倍数。来看一下free-list的节点结构。
free-list节点结构

这里又体现了SGI STL高效的设计思想。对于free-list节点的设计,采用了union结构体,当节点未分配时,节点指针free_list_link指向下一个空闲节点;当节点分配出去时,节点成员client_data即为客户端可用的内存空间,这样同一块内存两种用法,提高了内存使用效率。

下面是第二级配置器模板的部分实现,可以从中先看出一些端倪。
第二级配置器模板的部分实现
可以看到,第二级配置器的公开接口也有3个函数,和第一级配置器一样,分别是allocate,deallocate和reallocate。其实现策略会在下面描述。

来看一下第二级配置器的空间配置函数allocate()源码。
allocate()源码

身为一个配置器,__default_alloc_template拥有配置器的标准接口allocate()。此函数首先判断区块大小,大于128bytes就调用第一级配置器,小于128bytes就检查free-list。如果free-list有可用区块,就直接分配出去;如果没有可用区块,就把内存大小上调(round up)至8的倍数,然后调用refill()函数,从内存之获取空间来填充free-list,并重新分配。
区块自free-list调出的操作,如下图所示。
区块自free-list调出

身为一个配置器,__default_alloc_template拥有配置器标准接口函数deallocate()。函数首先判断区块大小,如果大于128bytes,就直接调用第一级配置器的deallocate,否则把内存区块放入对应的free-list中,以回收区块。源码和示意图图下图所示。
deallocate源码和示意图