内存池(MemPool)技术详解

本文详细介绍内存池技术原理及其在高效内存管理中的应用。经典内存池技术通过预分配内存块提高小对象的分配效率,减少碎片并加速内存回收过程。文中还对比了boost::pool与传统内存池的区别。
  内存池(MemPool)技术详解

概述

内存池(MemPool)技术备受推崇。我用google搜索了下,没有找到比较详细的原理性的文章,故此补充一个。另外,补充了boost::pool组件与经典MemPool的差异。同时也描述了MemPool在sgi-stl/stlport中的运用。

 

经典的内存池技术

 
经典的内存池(MemPool)技术,是一种用于分配大量大小相同的小对象的技术。通过该技术可以极大加快内存分配/释放过程。下面我们详细解释其中的奥妙。
 
经典的内存池只涉及两个常量:MemBlockSize、ItemSize(小对象的大小,但不能小于指针的大小,在32位平台也就是不能小于4字节),以及两个指针变量MemBlockHeader、FreeNodeHeader。开始,这两个指针均为空。
 
class  MemPool
{
private :
    const int m_nMemBlockSize;
    
const int m_nItemSize;

     struct  _FreeNode {
        _FreeNode
*  pPrev;
        BYTE data[m_nItemSize - sizeof(_FreeNode*)];
    };

    struct _MemBlock {
        _MemBlock
* pPrev;
        _FreeNode data[m_nMemBlockSize/m_nItemSize];
    };
 
 
    _MemBlock *  m_pMemBlockHeader;
    _FreeNode
*  m_pFreeNodeHeader;
 
public :
   MemPool(
int  nItemSize,  int  nMemBlockSize  =   2048 )
       : m_nItemSize(nItemSize), m_nMemBlockSize(nMemBlockSize),
         m_pMemBlockHeader(NULL), m_pFreeNodeHeader(NULL)
   {
   }
};
 
其中指针变量MemBlockHeader是把所有申请的内存块(MemBlock)串成一个链表,以便通过它可以释放所有申请的内存。FreeNodeHeader变量则是把所有自由内存结点(FreeNode)串成一个链。
 
这段话涉及两个关键概念: 内存块(MemBlock)自由内存结点(FreeNode)。内存块大小一般固定为MemBlockSize字节(除去用以建立链表的指针外)。内存块在申请之初就被划分为多个内存结点(Node),每个Node大小为ItemSize(小对象的大小),计MemBlockSize/ItemSize个。这MemBlockSize/ItemSize个内存结点刚开始全部是自由的,他们被串成链表。我们看看申请/释放内存过程,就很容易明白这样做的目的。
 

申请内存过程

代码如下:
void *  MemPool::malloc()     //  没有参数
{
    
if  (m_pFreeNodeHeader  ==  NULL)
    {
       
const   int  nCount  =  m_nMemBlockSize / m_nItemSize;
        _MemBlock
*  pNewBlock  =   new _MemBlock ;
        pNewBlock->data[ 0 ].pPrev  =  NULL;
        
for  ( int  i  =   1 ; i  <  nCount;  ++ i)
            pNewBlock->data[i].pPrev 
=   & pNewBlock->data[i - 1 ];
        m_pFreeNodeHeader 
=   & pNewBlock->data[nCount - 1 ];
        pNewBlock
-> pPrev  =  m_pMemBlock;
        m_pMemBlock 
=  pNewBlock;
    }
    
void *  pFreeNode  =  m_pFreeNodeHeader;
    m_pFreeNodeHeader 
=  m_pFreeNodeHeader -> pPrev;
    
return  pFreeNode;
}
 
内存申请过程分为两种情况:
  • 在自由内存结点链表(FreeNodeList)非空。
    在此情况下,Alloc过程只是从链表中摘下一个结点的过程。
     
  • 否则,意味着需要一个新的内存块(MemBlock)。
    这个过程需要将新申请的MemBlock切割成多个Node,并把它们串起来。
    MemPool技术的开销主要在这。
     

释放内存过程

 代码如下:
void  MemPool::free( void *  p)
{
    _FreeNode
*  pNode  =  (_FreeNode * )p;
    pNode
-> pPrev  =  m_pFreeNodeHeader;
    m_pFreeNodeHeader 
=  pNode;
}
 
释放过程极其简单,只是把要释放的结点挂到自由内存链表(FreeNodeList)的开头即可。
 

性能分析

MemPool技术申请内存/释放内存均极其快(比 AutoFreeAlloc慢)。其内存分配过程多数情况下复杂度为O(1),主要开销在FreeNodeList为空需要生成新的MemBlock时。内存释放过程复杂度为O(1)。
 
 

boost::pool

boost::pool是内存池技术的变种。主要的变化如下:

  • MemBlock改为非固定长度(MemBlockSize),而是:第1次申请时m_nItemSize*32,第2次申请时m_nItemSize*64,第3次申请时m_nItemSize*128,以此类推。不采用固定的MemBlockSize,而采用这种做法预测模型(是的,这是一种用户内存需求的预测模型,其实std::vector的内存增长亦采用了该模型),是一个细节上的改良。
     
  • 增加了ordered_free(void* p) 函数。

    ordered_free区别于free的是,free把要释放的结点挂到自由内存链表(FreeNodeList)的开头,ordered_free则假设FreeNodeList是有序的,因此会遍历FreeNodeList把要释放的结点插入到合适的位置。

    我们已经看到,free的复杂度是O(1),非常快。但请注意ordered_free是比较费的操作,其复杂度是O(N)。这里N是FreeNodeList的大小。对于一个频繁释放/申请的系统,这个N很可能是个大数。这个boost描述得很清楚:http://www.boost.org/libs/pool/doc/interfaces/pool.html

注意:不要认为boost提供ordered_free是多此一举。后文我们会在讨论boost::object_pool时解释这一点。

 

基于内存池技术的通用内存分配组件 

 
sgi-stl把内存池(MemPool)技术进行发扬光大,用它来实现其最根本的allocator。
 
其大体的思想是,建立16个MemPool,<=8字节的内存申请由0号MemPool分配,<=16字节的内存申请由1号MemPool分配,<=24字节的内存有2号MemPool分配,以此类推。最后,>128字节的内存申请由普通的malloc分配。
 
 

注意


以上代码属于伪代码(struct _FreeNode、_MemBlock编译通不过),并且去除了出错处理。

<think>我们正在讨论C++内存池。用户要求了解内存池的概念、使用方法和实现方式。根据之前的引用内容,内存池是一种预先分配一大块内存并自行管理的技术,用于提高内存分配效率,减少内存碎片和系统调用的开销。 需要涵盖的内容: 1. 内存池的概念和解决的问题 2. 内存池的优势(对比常规内存分配) 3. 内存池的适用场景 4. 内存池的实现方式(可以结合引用中的代码示例) 5. 内存池的使用方法(如何调用) 注意:引用中提供了一些关于内存池实现的代码片段,我们可以参考这些内容,但需要整理和解释。 回答结构建议: 1. 内存池概述 2. 内存池解决的问题 3. 内存池的基本工作原理 4. 内存池的实现关键点(包括数据结构、分配与释放策略) 5. 使用示例(代码片段) 6. 性能对比(引用中的测试数据) 7. 注意事项 另外,根据引用[3]中的测试数据,内存池在大量分配和释放时性能优于传统的malloc/free。 最后,生成相关问题。 </think>### C++ 内存池详解 #### 1. **内存池概念** 内存池是**预先分配大块内存并自主管理**的技术,核心思想是: - 程序启动时申请大块连续内存作为"水池" - 分配内存时直接从池中提取,而非调用 `new/malloc` - 释放内存时归还到池中,而非调用 `delete/free` - 程序退出时统一释放全部内存[^2] **解决的问题**: - **性能优化**:减少系统调用开销(`malloc/free` 需陷入内核态) - **减少碎片**:连续分配避免内存碎片化 - **确定性**:分配时间恒定,避免传统分配器的抖动问题 --- #### 2. **实现原理** ##### 关键数据结构 ```cpp // 内存块(管理一组分配单元) class MemoryBlock { public: int nSize; // 块总大小 int nFree; // 剩余可用单元数 int nFirst; // 首个空闲单元索引 MemoryBlock* pNext; // 指向下一个内存块 char aData[1]; // 分配单元起始位置(柔性数组) }; // 内存池管理器 class MemoryPool { public: MemoryPool(int _nUnitSize, int _nGrowSize = 3, int _nInitCount = 3); void* Alloc(); void Free(void* pFree); private: int nUnitSize; // 每个单元字节数 int nGrowSize; // 池满时新增块数 int nInitCount; // 初始块包含单元数 MemoryBlock* pBlock;// 内存块链表头 }; ``` [^4] ##### 工作流程 1. **初始化**: - 根据 `nUnitSize` 和 `nInitCount` 创建首个内存块 - 块内单元通过链表连接:`单元N` 存储 `单元N+1` 的偏移量 2. **分配内存**: ```cpp void* MemoryPool::Alloc() { if (!pBlock || pBlock->nFree == 0) { // 池空时新增内存块 MemoryBlock* newBlock = new MemoryBlock(nUnitSize, nGrowSize); newBlock->pNext = pBlock; pBlock = newBlock; } // 定位首个空闲单元 void* pUnit = pBlock->aData + pBlock->nFirst * nUnitSize; // 更新空闲链表:nFirst 指向下一空闲单元 pBlock->nFirst = *(int*)pUnit; pBlock->nFree--; return pUnit; } ``` 3. **释放内存**: ```cpp void MemoryPool::Free(void* pFree) { // 计算单元在块内的偏移 MemoryBlock* pBlock = FindBlock(pFree); // 查找所属块 // 将释放单元插入空闲链表头部 *(int*)pFree = pBlock->nFirst; pBlock->nFirst = (static_cast<char*>(pFree) - pBlock->aData) / nUnitSize; pBlock->nFree++; } ``` --- #### 3. **性能优势** 引用[3]测试数据显示内存池显著提升性能(单位:秒): | 操作次数 | malloc/free | 内存池 | |--------------|------------|--------| | 100,000 | 0.01 | 0.01 | | 1,000,000 | 0.15 | 0.11 | | 10,000,000 | 1.26 | 0.60 | | 100,000,000 | 9.21 | 5.99 | | 1,000,000,000| 92.70 | 61.46 | [^3] **性能提升原理**: - $O(1)$ 时间复杂度分配/释放 - 减少系统调用及锁竞争 - CPU缓存友好(连续内存访问) --- #### 4. **使用场景** 1. **高频小对象分配** 如网络框架中数据包、游戏引擎的粒子系统 2. **实时性要求高的系统** 避免分配延迟导致的抖动 3. **嵌入式设备** 减少内存碎片,提高有限资源的利用率 4. **自定义数据结构** 为特定对象(如链表节点)定制专用内存池 --- #### 5. **使用示例** ```cpp // 创建内存池(每个单元16字节,初始3单元,每次新增3单元) MemoryPool pool(16, 3, 3); // 分配内存 void* p1 = pool.Alloc(); void* p2 = pool.Alloc(); // 使用内存 memcpy(p1, data, 10); // 释放内存 pool.Free(p1); pool.Free(p2); ``` --- #### 6. **注意事项** 1. **线程安全** 多线程环境需加锁或设计无锁结构 2. **对齐要求** 通过 `MEMPOOL_ALIGNMENT` 指定对齐(如8字节对齐)[^4] 3. **内存泄漏检测** 需实现跟踪机制,确保程序退出时全部释放 4. **对象构造** 纯内存池不调用构造函数,需配合 placement new: ```cpp MyClass* obj = new (pool.Alloc()) MyClass(); // 定位构造 obj->~MyClass(); // 显式析构 pool.Free(obj); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值