内存池(MemPool)技术详解

摘要:
经典内存池技术经典内存池是一种用于分配大量相同大小的小对象的技术。否则,这意味着需要一个新的内存块。MemPool技术的成本主要在于此。性能分析MemPool技术非常快速地请求和释放内存。Sgi-stl是一个基于内存池技术的通用内存分配组件,它继承了内存池技术,并使用它来实现其最基本的分配器。总的想法是建立16个MemPools。˂=8字节的内存应用程序由0号MemPool分配,˂=16字节的内存程序由1号MemPool分配,˂=24字节的内存申请由2号MemPoole分配,依此类推。

作者:许式伟

来源:http://blog.csdn.net/xushiweizh/archive/2006/11/22/1402967.aspx

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

经典的内存池技术

经典的内存池(MemPool)技术,是一种用于分配大量大小相同的小对象的技术。通过该技术可以极大加快内存分配/释放过程。下面我们详细解释其中的奥妙。

经典的内存池只涉及两个常量:MemBlockSizeItemSize(小对象的大小,但不能小于指针的大小,在32位平台也就是不能小于4字节),以及两个指针变量MemBlockHeaderFreeNodeHeader。开始,这两个指针均为空。

  1. classMemPool
  2. {
  3. private:
  4. constintm_nMemBlockSize;
  5. constintm_nItemSize;
  6. struct_FreeNode{
  7. _FreeNode*pPrev;
  8. BYTEdata[m_nItemSize-sizeof(_FreeNode*)];
  9. };
  10. struct_MemBlock{
  11. _MemBlock*pPrev;
  12. _FreeNodedata[m_nMemBlockSize/m_nItemSize];
  13. };
  14. _MemBlock*m_pMemBlockHeader;
  15. _FreeNode*m_pFreeNodeHeader;
  16. public:
  17. MemPool(intnItemSize,intnMemBlockSize=2048)
  18. :m_nItemSize(nItemSize),m_nMemBlockSize(nMemBlockSize),
  19. m_pMemBlockHeader(NULL),m_pFreeNodeHeader(NULL)
  20. {
  21. }
  22. };

其中指针变量MemBlockHeader是把所有申请的内存块(MemBlock)串成一个链表,以便通过它可以释放所有申请的内存。FreeNodeHeader变量则是把所有自由内存结点(FreeNode)串成一个链。

这段话涉及两个关键概念:内存块(MemBlock自由内存结点(FreeNode。内存块大小一般固定为MemBlockSize字节(除去用以建立链表的指针外)。内存块在申请之初就被划分为多个内存结点(Node),每个Node大小为ItemSize(小对象的大小),计MemBlockSize/ItemSize个。这MemBlockSize/ItemSize个内存结点刚开始全部是自由的,他们被串成链表。我们看看申请/释放内存过程,就很容易明白这样做的目的。

申请内存过程

代码如下:

  1. void*MemPool::malloc()//没有参数
  2. {
  3. if(m_pFreeNodeHeader==NULL)
  4. {
  5. constintnCount=m_nMemBlockSize/m_nItemSize;
  6. _MemBlock*pNewBlock=new_MemBlock;
  7. pNewBlock->data[0].pPrev=NULL;
  8. for(inti=1;i<nCount;++i)
  9. pNewBlock->data[i].pPrev=&pNewBlock->data[i-1];
  10. m_pFreeNodeHeader=&pNewBlock->data[nCount-1];
  11. pNewBlock->pPrev=m_pMemBlock;
  12. m_pMemBlock=pNewBlock;
  13. }
  14. void*pFreeNode=m_pFreeNodeHeader;
  15. m_pFreeNodeHeader=m_pFreeNodeHeader->pPrev;
  16. returnpFreeNode;
  17. }

内存申请过程分为两种情况:

·在自由内存结点链表(FreeNodeList)非空。
在此情况下,Alloc过程只是从链表中摘下一个结点的过程。

·否则,意味着需要一个新的内存块(MemBlock)
这个过程需要将新申请的MemBlock切割成多个Node,并把它们串起来。
MemPool
技术的开销主要在这。

释放内存过程

代码如下:

  1. voidMemPool::free(void*p)
  2. {
  3. _FreeNode*pNode=(_FreeNode*)p;
  4. pNode->pPrev=m_pFreeNodeHeader;
  5. m_pFreeNodeHeader=pNode;
  6. }

释放过程极其简单,只是把要释放的结点挂到自由内存链表(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)。这里NFreeNodeList的大小。对于一个频繁释放/申请的系统,这个N很可能是个大数。这个boost描述得很清楚:http://www.boost.org/libs/pool/doc/interfaces/pool.html

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

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


sgi-stl
把内存池(MemPool)技术进行发扬光大,用它来实现其最根本的allocator

其大体的思想是,建立16MemPool<=8字节的内存申请由0MemPool分配,<=16字节的内存申请由1MemPool分配,<=24字节的内存有2MemPool分配,以此类推。最后,>128字节的内存申请由普通的malloc分配。

注意


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

免责声明:文章转载自《内存池(MemPool)技术详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇聊聊 Python 数据处理全家桶(Sqlite篇)iframe中获取上一级框架的HTML元素或JS下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

内存池技术的原理与实现

6.1 自定义内存池性能优化的原理  如前所述,读者已经了解到"堆"和"栈"的区别。而在编程实践中,不可避免地要大量用到堆上的内存。例如在程序中维护一个链表的数据结构时,每次新增或者删除一个链表的节点,都需要从内存堆上分配或者释放一定的内存;在维护一个动态数组时,如果动态数组的大小不能满足程序需要时,也要在内存堆上分配新的内存空间。 6.1.1 默认内存管...

DPDK数据包与内存专题-mempool内存池

前言:DPDK提供了内存池机制,使得内存的管理的使用更加简单安全。在设计大的数据结构时,都可以使用mempool分配内存,同时,mempool也提供了内存的获取和释放等操作接口。对于数据包mempool甚至提供了更加详细的接口-rte_pktmbuf_pool_create(),接下来重点分析通用的内存池相关内容。使用DPDK-17.02版本。 一. me...

一种高效快速的内存池实现(附源码)

此算法灵感来自于apache内存池实现原理,不过读者如果没有看过apache内存池实现也无关系,因为本算法相对apache内存池算法更为简单而且易懂,个人认为某些场合也更为高效,或许真正到了apache服务器上性能不如,但是这套设计思想应该还是可以借鉴到更多场合的。 我们在调用malloc函数时,操作系统内部会查找一个所谓的空闲链表,当找到足够大的空闲空间...

CIOCPServer的数据结构定义及内存池方案

为了避免频繁的申请释放内存,使用内存池来管理缓冲区对象和客户上下文对象使用的内存。 使用指针保存所有空闲的内存块,形成空闲列表。 申请内存时,这个指针不为NULL,就从空闲列表中取出一个来使用,如果取完,就真正的申请内存。                   1 缓冲区对象                程序使用CIOCPBuffer来描述per-IO数...

Netty 系列之 Netty 高性能之道

http://www.infoq.com/cn/articles/netty-high-performance 1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用。相比于传统基于Java序列化+BIO(同步阻...

超全!iOS 面试题汇总

作者:Job_Yang 之前看了很多面试题,感觉要不是不够就是过于冗余,于是我将网上的一些面试题进行了删减和重排,现在分享给大家。(题目来源于网络,侵删) 1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-c的类不可以多重继承;可以实现多个接口,通过实现多...