C++11实现高效内存池
前言
本篇文章主要内容为讲述自己对于C++11内存池项目(一位大佬所写)的解析(注意这里是我记录一下自己对项目的解析)。初次上手项目,很多知识点都没有遇到过,有些地方会提供其他的博文帮助理解,有描述不清楚或存在错误的地方还请大家一一指出。
【源码剖析】MemoryPool —— 简单高效的内存池 allocator 实现
项目源码:GitHub源码
项目介绍
内存池是什么
话不多说,这里摘录最具权威的原作者对于项目的解释:
什么是内存池?什么是 C++ 的 allocator?
内存池简单说,是为了减少频繁使用 malloc/free new/delete 等系统调用而造成的性能损耗而设计的。当我们的程序需要频繁地申请和释放内存时,频繁地使用内存管理的系统调用可能会造成性能的瓶颈,嗯,是可能,毕竟操作系统的设计也不是盖的(麻麻说把话说太满会被打脸的(⊙v⊙))。内存池的思想是申请较大的一块内存(不够时继续申请),之后把内存管理放在应用层执行,减少系统调用的开销。
allocator详解
那么,allocator 呢?它默默的工作在 C++ 所有容器的内存分配上。默默贴几个链接吧:
当你对 allocator 有基本的了解之后,再看这个项目应该会有恍然大悟的感觉,因为这个内存池是以一个 allocator 的标准来实现的。一开始不明白项目里很多函数的定义是为了什么,结果初步了解了 allocator 后才知道大部分是标准接口。这样一个 memory pool allocator 可以与大多数 STL 容器兼容,也可以应用于你自定义的类。像作者给出的例子 —— test.cpp, 是用一个基于自己写的 stack 来做 memory pool allocator 和 std::allocator 性能的对比 —— 最后当然是 memory pool allocator 更优。
内存池是一个一个的 block 以链表的形式连接起来,每一个 block 是一块大的内存,当内存池的内存不足的时候,就会向操作系统申请新的
block 加入链表。还有一个 freeSlots_ 的链表,链表里面的每一项都是对象被释放后归还给内存池的空间,内存池刚创建时
freeSlots_> 是空的,之后随着用户创建对象,再将对象释放掉,这时候要把内存归还给内存池,怎么归还呢?就是把指向这个对象的内存的指针加到
freeSlots_ 链表的前面(前插)。
用户在创建对象的时候,先检查 freeSlots_ 是否为空,不为空的时候直接取出一项作为分配出的空间。否则就在当前 block 内取出一个 Slot_ 大小的内存分配出去,如果 block 里面的内存已经使用完了呢?就向操作系统申请一个新的 block。内存池工作期间的内存只会增长,不释放给操作系统。直到内存池销毁的时候,才把所有的 block delete 掉。
注释源码: 点我到注释源码
下图左一为刚申请的Blcok↓

MemoryPool.tcc
该文件对Memory.h文件中声明过的函数进行实现,在此一些简单的类函数就不再一一赘述,只挑选出部分函数给出详细剖析,其余可在最后的代码汇总中查看注释。函数中使用了大量的强制转换,这里贴几个有关博客:
【C++】四种强制类型转换
c++类型转换
allocateBlock 创建Block块
函数用于申请一块Block内存块,并使用头插法放入Blcok内存块链表。
//申请一块新的分区block
template <typename T, size_t BlockSize>
void
MemoryPool<T, BlockSize>::allocateBlock()
{
// Allocate space for the new block and store a pointer to the previous one
//申请 BlockSize字节大小的一块空间并用char* 类型指针接收
data_pointer_ newBlock = reinterpret_cast<data_pointer_>
(operator new(BlockSize));
//newBlock成为新的block内存首址
reinterpret_cast<slot_pointer_>(newBlock)->next = currentBlock_;
//currentBlock 更新为 newBlock的位置
currentBlock_ = reinterpret_cast<slot_pointer_>(newBlock);
// Pad block body to staisfy the alignment requirements for elements
//保留第一个slot 用于Block链表的链接
data_pointer_ body = newBlock + sizeof(slot_pointer_);
//求解空间对齐需要的字节数
size_type bodyPadding = padPointer(body, alignof(slot_type_));
//若出现残缺不可以作为一个slot的空间,跳过这块空间作为第一个可用slot的位置
currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
lastSlot_ = reinterpret_cast<slot_pointer_>
(newBlock + BlockSize - sizeof(slot_type_) + 1);
//始址: newBlock 块大小 BlockSize 末址 newBlock + BlockSize 末址减去一个slot槽大小
//得到倒数第二个slot的末址 再加一得到最后一块slot的始址
}
这里需要注意的是:在生成一块BlockSize字节大小的Block内存块时需要保留Slot类型大小的空间用于后续Block链表的链接。对应图中的蓝色内存区。此外,在调用padPointer函数 的时候传入的参数使用了 C++11 的新特性 C++11 内存对齐 alignof alignas
padPointer 空间对齐
每一个Block内存块的大小(BlockSize字节)并不是刚好为Slot槽大小的整数倍(取决于使用者定义的BlockSize),例如一个内存块Block大小设置为BlockSize = 11 bytes,每个Slot槽存放数据的大小为4字节时,BlockSize % sizeof(Slot) = 11 % 4 = 2 … 3,余下的三个字节是不足一个Slot槽所需空间的,这时我们必须略去这些多余的空间。上述空间在图中用橙色区域进行了表示。

//计算对齐所需补的空间
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::padPointer(data_pointer_ p, size_type align)
const noexcept
{
uintptr_t result = reinterpret_cast<uintptr_t>(p);//将char* 类型的指针转换为 uintptr_t 类型(无符号整型)
return ((align - result) % align);//多余不够一个Slot槽大小的空间,需要跳过
}
MemoryPool 构造函数
这里说明一下内存池中使用到的指针,先看看原作者是怎么描述的:
slot_pointer_ currentBlock_; // 内存块链表的头指针
slot_pointer_ currentSlot_; // 元素链表的头指针
slot_pointer_ lastSlot_; // 可存放元素的最后指针
slot_pointer_ freeSlots_; // 元素构造后释放掉的内存链表头指针
当时阅读项目时很不解的点就是 currentSlot 指针的含义,作者给出的解释是元素链表的头指针 ,我思来想去,每一个Slot槽是一个联合体:
union Slot_ {
value_type element;//使用时为 value_type 类型
Slot_* next;//需要回收时为 Slot_* 类型并加入 空闲链表中
};

当Slot用来存放数据时,根据联合的特性,另一个指针属性是不可以使用的,这两个属性的关系也很简单,不可同时出现。因此当Slot槽存储数据时不可能同时是一个链表(链表至少需要一个指针),正是这样,再根据后面对于currentSlot指针的使用我将其解释为
指向第一个可用元素 Slot ,这是狭义上的第一个可用,在后面allocate函数中为每一个元素分配空间的时候currentSlot指针是明确的只能往后走的,也就是说这个 “可用” 不包括已经在空闲链表上的Slot(使用过但现在释放并还给内存池的槽),指向的是从没有被置放过数据的Slot槽。
//构造函数 初始化所有指针为 nullptr
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool()
noexcept
{
currentBlock_ = nullptr;//指向第一块Block区 即Block内存块链表的头指针
currentSlot_ = nullptr;//当前第一个可用槽的位置
lastSlot_ = nullptr;//最后可用Slot的位置
freeSlots_ = nullptr;//空闲链表头指针
}
allocate 内存分配
为每个element元素分配内存,先查询槽的空闲链表是否为空,若不为空则直接将链表头结点表示的空间分配出去,反之到Block中分割新的Slot大小的空间。
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::allocate(size_type n, const_pointer hint)
{
//在内存池中申请 n 个Node,hint 默认设置为 空
if (freeSlots_ !=

本文深入解析C++11内存池项目,探讨内存池原理及allocator设计,重点介绍MemoryPool类的构造、内存分配与释放机制。
最低0.47元/天 解锁文章
1058





