资讯动态

C++内存池介绍与经典内存池的实现

发布时间:2026/9/28 1:01:08 来源:尧图企业网站定制
代码编译运行环境VS2012DebugWin32文章目录1.默认内存管理函数的不足2.内存池简介2.1 内存池的定义2.2 内存池的优点2.3 内存池的分类3.经典的内存池技术3.1 经典内存池的设计3.1.1 经典内存池实现过程3.1.2 经典内存池数据结构设计3.2 经典内存池的实现3.3 程序分析参考文献1.默认内存管理函数的不足利用默认的内存管理操作符 new/delete 和函数 malloc()/free() 在堆上分配和释放内存会有一些额外的开销。系统在接收到分配一定大小内存的请求时首先查找内部维护的内存空闲块表并且需要根据一定的算法例如分配最先找到的不小于申请大小的内存块给请求者或者分配最适于申请大小的内存块或者分配最大空闲的内存块等找到合适大小的空闲内存块。如果该空闲内存块过大还需要切割成已分配的部分和较小的空闲块。然后系统更新内存空闲块表完成一次内存分配。类似地在释放内存时系统把释放的内存块重新加入到空闲内存块表中。如果有可能的话可以把相邻的空闲块合并成较大的空闲块。默认的内存管理函数还考虑到多线程的应用需要在每次分配和释放内存时加锁同样增加了开销。可见如果应用程序频繁地在堆上分配和释放内存会导致性能的损失。并且会使系统中出现大量的内存碎片降低内存的利用率。默认的分配和释放内存算法自然也考虑了性能然而这些内存管理算法的通用版本为了应付更复杂、更广泛的情况需要做更多的额外工作。而对于某一个具体的应用程序来说适合自身特定的内存分配释放模式的自定义内存池可以获得更好的性能。2.内存池简介2.1 内存池的定义内存池Memory Pool是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请内存这样做的缺点在于所申请内存块的大小不定当频繁使用时会造成大量的内存碎片并进而降低性能。2.2 内存池的优点内存池则是在真正使用内存之前预先申请分配一定数量、大小相等一般情况下的内存块留作备用。当有新的内存需求时就从内存池中分出一部分内存块若内存块不够再继续申请新的内存。这样做的一个显著优点是使得内存分配效率得到提升。2.3 内存池的分类应用程序自定义的内存池根据不同的适用场景又有不同的类型。从线程安全的角度来分内存池可以分为单线程内存池和多线程内存池。单线程内存池整个生命周期只被一个线程使用因而不需要考虑互斥访问的问题多线程内存池有可能被多个线程共享因此需要在每次分配和释放内存时加锁。相对而言单线程内存池性能更高而多线程内存池适用范围更加广泛。从内存池可分配内存单元大小来分可以分为固定内存池和可变内存池。所谓固定内存池是指应用程序每次从内存池中分配出来的内存单元大小事先已经确定是固定不变的而可变内存池则每次分配的内存单元大小可以按需变化应用范围更广而性能比固定内存池要低。3.经典的内存池技术内存池技术因为其对内存管理有着显著的优点在各大项目中广泛应用备受推崇。但是通用的内存管理机制要考虑很多复杂的具体情况如多线程安全等难以对算法做有效的优化所以在一些特殊场合实现特定应用环境的内存池在一定程度上能够提高内存管理的效率。经典内存池技术是一种用于分配大量大小相同的小对象的技术。通过该技术可以极大加快内存分配/释放过程。既然是针对特定对象的内存池所以内存池一般设置为类模板根据不同的对象来进行实例化。3.1 经典内存池的设计3.1.1 经典内存池实现过程1先申请一块连续的内存空间该段内存空间能够容纳一定数量的对象2每个对象连同一个指向下一个对象的指针一起构成一个内存节点Memory Node。各个空闲的内存节点通过指针形成一个链表链表的每一个内存节点都是一块可供分配的内存空间3某个内存节点一旦分配出去从空闲内存节点链表中去除4一旦释放了某个内存节点的空间又将该节点重新加入空闲内存节点链表5如果一个内存块的所有内存节点分配完毕若程序继续申请新的对象空间则会再次申请一个内存块来容纳新的对象。新申请的内存块会加入内存块链表中。经典内存池的实现过程大致如上面所述其形象化的过程如下图所示如上图所示申请的内存块存放三个可供分配的空闲节点。空闲节点由空闲节点链表管理如果分配出去将其从空闲节点链表删除如果释放将其重新插入到链表的头部。如果内存块中的空闲节点不够用则重新申请内存块申请的内存块由内存块链表来管理。注意本文涉及到的内存块链表和空闲内存节点链表的插入为了省去遍历链表查找尾节点便于操作新节点的插入均是插入到链表的头部而非尾部。当然也可以插入到尾部读者可自行实现。3.1.2 经典内存池数据结构设计按照上面的过程设计内存池类模板有这样几个成员。两个指针变量内存块链表头指针pMemBlockHeader空闲节点链表头指针pFreeNodeHeader空闲节点结构体structFreeNode{FreeNode*pNext;chardata[ObjectSize];};内存块结构体structMemBlock{MemBlock*pNext;FreeNode data[NumofObjects];};3.2 经典内存池的实现根据以上经典内存池的设计编码实现如下。#includeiostreamusingnamespacestd;templateintObjectSize,intNumofObjects20classMemPool{private://空闲节点结构体structFreeNode{FreeNode*pNext;chardata[ObjectSize];};//内存块结构体structMemBlock{MemBlock*pNext;FreeNode data[NumofObjects];};FreeNode*freeNodeHeader;MemBlock*memBlockHeader;public:MemPool(){freeNodeHeaderNULL;memBlockHeaderNULL;}~MemPool(){MemBlock*ptr;while(memBlockHeader){ptrmemBlockHeader-pNext;deletememBlockHeader;memBlockHeaderptr;}}void*malloc();voidfree(void*);};// 分配空闲的结点。templateintObjectSize,intNumofObjectsvoid*MemPoolObjectSize,NumofObjects::malloc(){//无空闲节点申请新内存块if(freeNodeHeaderNULL){MemBlock*newBlocknewMemBlock;newBlock-pNextNULL;freeNodeHeadernewBlock-data[0];//设置内存块的第一个节点为空闲节点链表的首节点//将内存块的其它节点串起来for(inti1;iNumofObjects;i){newBlock-data[i-1].pNextnewBlock-data[i];}newBlock-data[NumofObjects-1].pNextNULL;// 首次申请内存块if(memBlockHeaderNULL){memBlockHeadernewBlock;}else{// 将新内存块加入到内存块链表。newBlock-pNextmemBlockHeader;memBlockHeadernewBlock;}}// 返回空节点闲链表的第一个节点。void*freeNodefreeNodeHeader;freeNodeHeaderfreeNodeHeader-pNext;returnfreeNode;}// 释放已经分配的结点。templateintObjectSize,intNumofObjectsvoidMemPoolObjectSize,NumofObjects::free(void*p){FreeNode*pNode(FreeNode*)p;pNode-pNextfreeNodeHeader;//将释放的节点插入空闲节点头部freeNodeHeaderpNode;}classActualClass{staticintcount;intNo;public:ActualClass(){Nocount;count;}voidprint(){coutthis: ;coutthe Noth objectendl;}void*operatornew(size_t size);voidoperatordelete(void*p);};// 定义内存池对象MemPoolsizeof(ActualClass),2mp;void*ActualClass::operatornew(size_t size){returnmp.malloc();}voidActualClass::operatordelete(void*p){mp.free(p);}intActualClass::count0;intmain(){ActualClass*p1newActualClass;p1-print();ActualClass*p2newActualClass;p2-print();deletep1;p1newActualClass;p1-print();ActualClass*p3newActualClass;p3-print();deletep1;deletep2;deletep3;}程序运行结果004AA214: the 0th object 004AA21C: the 1th object 004AA214: the 2th object 004AB1A4: the 3th object3.3 程序分析阅读以上程序应注意以下几点。1对一种特定的类对象而言内存池中内存块的大小是固定的内存节点的大小也是固定的。内存块在申请之初就被划分为多个内存节点每个 Node 的大小为 ItemSize。刚开始所有的内存节点都是空闲的被串成链表。2成员指针变量 memBlockHeader 是用来把所有申请的内存块连接成一个内存块链表以便通过它可以释放所有申请的内存。freeNodeHeader 变量则是把所有空闲内存节点串成一个链表。freeNodeHeader为空则表明没有可用的空闲内存节点必须申请新的内存块。3申请空间的过程如下。在空闲内存节点链表非空的情况下malloc 过程只是从链表中取下空闲内存节点链表的头一个节点然后把链表头指针移动到下一个节点上去。否则意味着需要一个新的内存块。这个过程需要申请新的内存块切割成多个内存节点并把它们串起来内存池技术的主要开销就在这里。4释放对象的过程就是把被释放的内存节点重新插入到内存节点链表的开头。最后被释放的节点就是下一个即将被分配的节点。5内存池技术申请/释放内存的速度很快其内存分配过程多数情况下复杂度为 O(1)主要开销在 freeNodeHeader 为空时需要生成新的内存块。内存节点释放过程复杂度为 O(1)。6 在上面的程序中指针 p1 和 p2 连续两次申请空间它们代表的地址之间的差值为 8正好为一个内存节点的大小sizeof(FreeNode)。指针 p1 所指向的对象被释放后再次申请空间得到的地址与刚刚释放的地址正好相同。指针 p3 多代表的地址与前两个对象的地址相聚很远原因是第一个内存块中的空闲内存节点已经分配完了p3 指向的对象位于第二个内存块中。以上内存池方案并不完美比如只能单个单个申请对象空间不能申请对象数组内存池中内存块的个数只能增大不能减少未考虑多线程安全等问题。现在已经有很多改进的方案请读者自行查阅相关资料。参考文献C 应用程序性能优化第 6 章内存池陈刚.C高级进阶教程[M].武汉:武汉大学出版社,2008.7.8什么是内存池技术

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑