STL里面的Allocator是用来管理空间分配的,这里的空间基本上指的就是内存,管理内存是影响到很多STL的容器性能的主要因素之一,所以对Allocator的了解需要非常深入而不是仅仅知道如何调用.笔者前段时间大致的看了侯捷老师的那本«STL源码剖析»,对SGI版本的STL算是有点简略的认识,可是这种走马观花的认识也只能帮助我理解其大致机理,当遇到真正让你实现一个简单的Allocator时候就会显得束手无策,所以有必要通过编码,简单临摹一下SGI的Allocator.
简单粗暴Naive方式实现Allocator
我们可以编写一个最简单的符合STL规范的Naive版本的Allocator,它起码需要allocate/deallocate函数用来申请/释放空间,construct/destroy函数用来在内存空间上构造和析构对象, 还有rebind成员模板以及一些类型别名等.
这个Allocator只是简单的申请/释放空间,没有任何特别之处.
上面用到的_allocate, _deallocate, _construct和_destroy函数定义如下:
SGI版本的两级Allocator实现
SGI的Allocator有两个版本,一个是类似上面提到的Naive版本,它是为了兼容通用的STL标准,但是这个版本SGI从来不用,而是使用两级的Allocator,这个Allocator与STL标准不兼容,只能被SGI-STL的容器使用,这里就简单介绍一下SGI两级Allocator的设计思路.
两级Allocator
> SGI的Allocator分为一级和二级Allocator,其中一级Allocator负责管理大于128字节的内存请求,而二级Allocator负责小于等于128字节的内存请求.
一级Allocator
这里我实现的一级Allocator名字叫做superior_allocator,它的成员函数解释如下:
- allocate函数负责分配指定大小内存
- deallocate函数负责释放指定指针的内存
- reallocate函数负责再次申请扩充内存
- set_allocate_failed_handler函数用来接受使用者定义的内存不足处理函数,在遇到内存不足时候可以调用此函数期望释放部分内存
- oom_allocate函数用来处理allocate函数分配内存时候由于内存不足而失败这种情况
- oom_reallocate函数用来处理reallocate函数分配内存时候由于内存不足而失败这种情况
其中具体函数实现如下:
从实现代码可以看出来,一级Allocator其实也很简单,和上面提到的Naive方式几乎差不多,就是malloc/free/realloc, 遇到malloc/realloc失败的话不断的调用用户处理函数以释放部分内存.之所以这么简单,是因为一级Allocator一般只处理较大内存块的分配管理,所以可以不用像二级Allocator那么斤斤计较,管理严密.
二级Allocator
这里我编写的二级Allocator名字叫做junior_allocator,它是完全按照SGI的设计思路实现的,负责较小内存块的管理.
free_list指针数组
要了解SGI二级Allocator的原理就必须先了解free_list指针数组,free_list指针数组的原型如下:
这里势必要介绍一下obj的原型:
这个obj的设计非常巧妙,是一个union,这样设计有以下几点好处:
- 一个obj的地址也就是其data指针值,这样方便通过obj的data值读出其内存地址.
> 例如, 我们有obj o那么o.data其实就是&o, 也就执行o的具体位置
- 一个obj的next字段用来存储指向下个节点的位置,这样能形成类似链表一样的结构.
- 通过union结合就可以避免单独开辟空间存储指针的空间浪费
结合obj的原型我们再来看free_list指针数组,我们这里free_list指针数组的大小FREE_COUNT = 128 / 8 = 16, 于是有16个obj指针,这16个obj指针分别指向第一个空闲的8字节,16字节,…,128字节内存块的位置
我们可以初始化时候为每个free_list[i]申请连续20个相应块空间,作为备用,初始化之后的内存使用情形如下图:
上图的free_lst指针数组中的指针指向其对应第一个空闲块,然后后续空闲块通过神奇的obj*连接而成,这里就可以体现到obj设计的精妙了,它一点也不浪费空间,而且还是非常精简的.
另外,对于上图需要解释的就是start_free到end_free这块空间就是junior_allocator分配的还未被使用的空闲内存池.
allocate和deallocate的实现
reallocate基本上就是调用allocate和deallocate,这里就不作表述.
##### allocate过程
我们试图从junior_allocator申请大小为n字节的内存,如果n > 128,那么交给superior_allocator完成,否则先根据n的大小算出相应内存块大小,比如n = 7那么内存块大小就是8.接着从free_list中找出相应的内存块第一个空闲内存地址free_list[i],如果此时有空闲块,那么返回此块地址,并且free_list[i] = free_list[i]->next
即指向下一个空闲块;如果不存在空闲块,那么试图从空闲的内存池中申请一块连续的内存,然后切分成free_list[i]对应内存块大小的小块,然后更新free_list[i].
这里我们可以看到,每次某个内存块不够用时候就从内存池取出新的加入,总有内存池用完的时候,当内存池用完了该怎么办呢?这里有两个解决办法:
- 通过malloc再申请一快大内存池
- 如果系统所有内存都不够了,那只能从其他free_list中扣除一些内存过来了,因为每个free_list[i]都分配了一定数量的内存块,而有些free_list[i]可能用的很少,所以尚存空闲块,那就把它们的抢占过来.
deallocate
deallocate实现非常简单,如果释放的空间大于128字节,那么就交给一级allocator处理;如果不是,则把内存块加到对应free_list[i]中,通常做法就是直接插入大free_list[i]对应链表的表头.
代码实现
junior_allocator类定义
superior_allocator成员函数具体实现
后记
SGI的两级Allocator实现非常优美,特别是junior_allocator的实现,通过严密的管理策略使得几乎不会产生内存碎片.
另外,上面的代码只是笔者简单的模仿实现,SGI的源码则是更加的完整和健壮,并且对多线程情况也做出处理.
- EOF -
声明:本文采用BY-NC-SA协议进行授权.转载请注明: 仿照实现SGI-STL空间配置器
comments powered by