4.2
内存分配器的工作方式
(本节内容可以参考操作系统书籍,cuigang)
内存分配器如何工作?它管理一个由raw bytes所组成的内存池。薄记结构可以简单如下:
struct MemControlBlock{
std::size_t size_;
bool available_;
};
MemControlBlock对象管理的内存紧随其后,大小 size_,然后是另一个控制块。初始时,内存池中只有一个MemControlBlock,并将所有内存视为一大块,这就是所谓root控制块,永不离开最初位置。以
+===================+=================+==================+
| available_ : ture | size_ : 1048571 | mem[1048571] |
+===================+=================+==================+
|
|
|-----> 1 byte <----|----> 4 bytes <--|-> 1048571bytes <-|
|-----------------------> 1048576 bytes <----------------|
每次分配都引发一次线性查找,找到一个合适区块,适合策略有最先匹配法则(first fit)、最佳匹配(best
fit),最差匹配(worst fit),甚至随机匹配(random fit)。有趣的是最差匹配比最佳匹配好!
每次归还区块,同样需要一次线性搜索,找出待归还区块的前一区块并调整大小。
如你所看,这一策略时间上并非高效。但空间上开销较小,甚至我们可以再调整:
//注意下面代码依赖编译器和平台
struct MemControlBlock{
std::size_t size_ : 31;
bool available_ : 1;
};
为了前序遍历,我们可以定义为双向链表:
struct MemControlBlock{
bool available_;
MemControlBlock* next_;
MemControlBlock* prev_;
};
这里我们不需要size_了,我们可以通过this->next - this 来得到。
尽管如此,分配动作还是得消耗线性时间。要减轻这样的消耗,有如多巧妙技术可用,但都各有利弊,存在某种情况下的不良性能(参考Knuth著作)。这里我们不对其讨论,我们的焦点是可最佳处理小型对象的“专用分配器”。
4.3
小型对象分配器
本章介绍的小型对象分配器分为4层结构。如图所示,下层提供功能供上层使用。
+-------------------+
| SmallObject |
+-------------------+
| SmallObjAllocator |
+-------------------+
| FixedAllocator |
+-------------------+
| Chunk
|
+-------------------+
最下层是Chunk对象,每一个Chunk管理一大块内存,此大块内存包含整数个固定大小的区块。可以用来分配和归还,当其中没有剩余时,分配失败返回零。
第二层是FixAllocator
class,其以Chunk为构件。主要用来满足那些“累计总量超过Chunk容量”的请求。FixAllocator通过一个array(实际是vector)组合Chunks。如果所有Chunk都被使用,FixAllocator分配新Chunk,并加入array,来满足需求。
第三层是SmallObjectAllocator提供通用分配/归还函数。拥有数个FixedAllocator对象,每个负责分配某特定大小对象。根据申请bytes个数不同,SmallObjAllocator对象会将内存分配申请分发。如果请求量过大,会转交系统new。
第四层是SmallObject,它包装FixedAllocator,以便向C++
classes提供封装良好的分配服务。SmallObject重载new和delete。你只需要让你的对象派生于SmallObject。