利用C/C++开发大型应用程序中,内存的管理与分配是一个需要认真考虑的部分。

本文描述了内存池设计原理并给出内存池的实现代码,代码支持Windows和Linux,多线程安全。

内存池设计过程中需要考虑好内存的分配与释放问题,其实也就是空间和时间的矛盾。

有的内存池设计得很巧妙,内存分配与需求相当,但是会浪费过多的时间去查找分配与释放,这就得不偿失;

实际使用中,我们更多的是关心内存分配的速度,而不是内存的使用效率。基于此,本文按照如下思想设计实现内存池。

主要包含三个结构:StiaticMemory, MemoryChunk和MemoryBlock,三者之间的关系如下图所示:


c++内存池实现_#define

1.内存的分配:

(1)如果分配大小超过1024,直接采用malloc分配,分配的时候多分配sizeof(size_t)字节,用于保存该块的大小;

(2)否则根据分配大小,查找到容纳该大小的最小size的MemoryChunk;

(3)查找MemoryChunk的链表指针pList,找到空闲的MemoryBlock返回;

(4)如果pList为NULL,临时创建MemoryBlock返回;

(5)MemoryBlock头部包含两个成员,pChunk指向的所属的MemoryChunk对象,size表明大小,其后才是给用户使用的空间;

2.内存的释放:

(1)根据释放的指针,查找器size头部,即减去sizeof(size_t)字节,判断该块的大小;

(2)如果大小超过1024,直接free;

(3)否则交给MemoryChunk处理,而块的头部保存了该指针,因此直接利用该指针就可以收回该内存。

注意的问题:

上述设计的内存池通过冗余的头部来实现内存块的分配与释放,减少了内存池的操作时间,速度上要优于原始的malloc和free操作,同时减少了内存碎片的增加。

但是该设计中没有去验证释放的块冗余头部的正确性,因此故意释放不属于内存池中的块或者修改头部信息都会导致内存池操作失败,当然这些可以由程序员来控制。

此外,内存池中分配出去的内存块如果不主动释放,内存池没有保留信息,不会自动释放,但是在退出的时候会验证验证是否完全释放,其实这个在系统测试时候就可以检测出来,我想这个缺陷也是可以弥补的,在此提出,希望使用者注意。

下面贴上源码,如果对代码有任何建议或者发现存在的Bug,希望与我联系,共同学习交流,Tx。

MemoryChunk.h 文件,线程安全


[html]  ​​view plain​​  ​​copy​​



  1. #ifndef MEMORY_CHUNK_H  
  2. #define MEMORY_CHUNK_H  
  3. #include <cstdio>  
  4. #include <cassert>  
  5. #include <cstdlib>  
  6.   
  7. #ifdef WIN32  
  8. #include <windows.h>  
  9. typedef CRITICAL_SECTION MUTEXTYPE;  
  10. #define INITMUTEX(hMutex) InitializeCriticalSection(&hMutex)  
  11. #define DELMUTEX(hMutex) DeleteCriticalSection(&hMutex)  
  12. #define LOCK(hMutex) EnterCriticalSection(&hMutex)  
  13. #define UNLOCK(hMutex) LeaveCriticalSection(&hMutex)  
  14. #else  
  15. #include <pthread.h>  
  16. typedef pthread_mutex_t MUTEXTYPE;  
  17. #define INITMUTEX(hMutex) pthread_mutex_init(&hMutex,NULL)  
  18. #define DELMUTEX(hMutex) pthread_mutex_destroy(&hMutex)  
  19. #define LOCK(hMutex) pthread_mutex_lock(&hMutex)  
  20. #define UNLOCK(hMutex) pthread_mutex_unlock(&hMutex)  
  21. #endif  
  22.   
  23. class MemoryChunk;  
  24.   
  25. /** @struct MemoryBlock  
  26.  *  
  27.  */  
  28. struct BlockHeader  
  29. {  
  30.     MemoryChunk* pChunk;  
  31.     size_t len;  
  32. };  
  33. struct MemoryBlock;  
  34. struct BlockData  
  35. {  
  36.     union{  
  37.         MemoryBlock* pNext;  
  38.         char pBuffer;  
  39.     };  
  40. };  
  41. struct MemoryBlock  
  42. {  
  43.     BlockHeader header;  
  44.     BlockData data;  
  45. };  
  46.   
  47. /** @class MemoryChunk  
  48.  *  
  49.  */  
  50.   
  51. class MemoryChunk  
  52. {  
  53. public:  
  54.     MemoryChunk(size_t size, int count)  
  55.     {  
  56.         INITMUTEX(hMutex);  
  57. >pFreeList=NULL;  
  58. >size=size;  
  59. >count=0;  
  60.         MemoryBlock* pBlock;  
  61.         while(count--){  
  62. pBlock=CreateBlock();  
  63.             if(!pBlock)break;  
  64. >data.pNext=pFreeList;  
  65. pFreeList=pBlock;  
  66.         }  
  67.     }  
  68.     ~MemoryChunk()  
  69.     {  
  70. tempcount=0;  
  71.         MemoryBlock* pBlock;  
  72. pBlock=pFreeList){  
  73. pFreeList=pBlock->data.pNext;  
  74.             DeleteBlock(pBlock);  
  75.             ++tempcount;  
  76.         }  
  77. tempcount==count);//!确保释放完全  
  78.         DELMUTEX(hMutex);  
  79.     }  
  80.     void* malloc()  
  81.     {  
  82.         MemoryBlock* pBlock;  
  83.         LOCK(hMutex);  
  84.         if(pFreeList){  
  85. pBlock=pFreeList;  
  86. pFreeList=pBlock->data.pNext;  
  87.         }  
  88.         else{  
  89. pBlock=CreateBlock())){  
  90.                 UNLOCK(hMutex);  
  91.                 return NULL;  
  92.             }  
  93.         }  
  94.         UNLOCK(hMutex);  
  95. >data.pBuffer;  
  96.     }  
  97.     static void free(void* pMem)  
  98.     {  
  99. pBlock=(MemoryBlock*)((char*)pMem-sizeof(BlockHeader));  
  100. >header.pChunk->free(pBlock);  
  101.     }  
  102.     void free(MemoryBlock* pBlock)  
  103.     {  
  104.         LOCK(hMutex);  
  105. >data.pNext=pFreeList;  
  106. pFreeList=pBlock;  
  107.         UNLOCK(hMutex);  
  108.     }  
  109.       
  110.     MemoryChunk* Next(){return pNext;}  
  111.   
  112. protected:  
  113.     MemoryBlock* CreateBlock()  
  114.     {  
  115. pBlock=(MemoryBlock*)::malloc(sizeof(BlockHeader)+size);  
  116.   
  117.         if(pBlock){  
  118.   
  119. >header.pChunk=this;  
  120. >header.len=size;  
  121.               
  122.             ++count;  
  123.         }  
  124.         return pBlock;  
  125.     }  
  126.     void DeleteBlock(MemoryBlock* pBlock)  
  127.     {  
  128.         ::free(pBlock);  
  129.     }  
  130. private:  
  131.     MemoryBlock* pFreeList;  
  132.     size_t size;//!Block大小  
  133.     int count;//!Block数目  
  134.     MemoryChunk* pNext;  
  135.     MUTEXTYPE hMutex;  
  136. };  
  137. #endif  


StaticMemory.h文件,内存池对象


[html]  ​​view plain​​  ​​copy​​



  1. #ifndef STATIC_MEMORY_H  
  2. #define STATIC_MEMORY_H  
  3. #include "MemoryChunk.h"  
  4. /** @ StaticMemory.h  
  5.  * 定义实现内存池  
  6.  * 采用固定大小策略进行内存管理与分配  
  7.  * 减少因大量小内存分配导致的内存碎片增加  
  8.  */  
  9. struct HeapHeader  
  10. {  
  11.     size_t size;  
  12. };  
  13. struct MemoryHeap  
  14. {  
  15.     HeapHeader header;  
  16.     char pBuffer;  
  17. };  
  18.   
  19. class StaticMemory  
  20. {  
  21. public:  
  22. MAX_SIZE=1024,MIN_SIZE=sizeof(MemoryChunk*)};  
  23.     StaticMemory()  
  24.     {  
  25. chunkcount=0;  
  26. size=MIN_SIZE; size<=MAX_SIZE; size*=2)++chunkcount;  
  27. pChunkList=(MemoryChunk**)malloc(sizeof(MemoryChunk*)*chunkcount);  
  28. pChunkList=new MemoryChunk*[chunkcount];  
  29. index=0;  
  30. size=MIN_SIZE; size<=MAX_SIZE; size*=2)  
  31.         {  
  32.             pChunkList[index++]=new MemoryChunk(size,1000);  
  33.         }  
  34.     }  
  35.     ~StaticMemory()  
  36.     {  
  37. index=0; index<chunkcount; ++index)  
  38.         {  
  39.             delete pChunkList[index];  
  40.         }  
  41.         //free(pChunkList);  
  42.         delete[] pChunkList;  
  43.     }  
  44.     void* Malloc(size_t size)  
  45.     {  
  46. >MAX_SIZE){  
  47.             return malloc(size);  
  48.         }  
  49. index=0;  
  50. tsize=MIN_SIZE; tsize<=MAX_SIZE; tsize*=2){  
  51. >=size)break;  
  52.             ++index;  
  53.         }  
  54. >malloc();  
  55.     }  
  56.     void Free(void* pMem)  
  57.     {  
  58.         if(!free(pMem))MemoryChunk::free(pMem);  
  59.     }  
  60. protected:  
  61.     void* malloc(size_t size)  
  62.     {  
  63. pHeap=(MemoryHeap*)::malloc(sizeof(HeapHeader)+size);  
  64.         if(pHeap){  
  65. >header.size=size;  
  66. >pBuffer;  
  67.         }  
  68.         return NULL;  
  69.     }  
  70.     bool free(void* pMem)  
  71.     {  
  72. pHeap=(MemoryHeap*)((char*)pMem-sizeof(HeapHeader));  
  73. >header.size>MAX_SIZE){  
  74.             ::free(pHeap);  
  75.             return true;  
  76.         }  
  77.         return false;  
  78.     }  
  79. private:  
  80.     MemoryChunk** pChunkList;  
  81.     int chunkcount;  
  82. };  
  83. #endif  


ObejctManager.h文件,用于实现对象的创建与管理,比较简易。


[html]  ​​view plain​​  ​​copy​​



  1. #ifndef OBJECT_MANAGER_H  
  2. #define OBJECT_MANAGER_H  
  3. #include "StaticMemory.h"  
  4. /** @class ObjectManager  
  5.  * 实现利用内存池创建对象  
  6.  * 要求对象具有缺省构造函数  
  7.  */  
  8. template<typename T>  
  9. class ObjectManager  
  10. {  
  11. public:  
  12.     typedef T ObjectType;  
  13.   
  14.     static ObjectType* Create(StaticMemory* pool)  
  15.     {  
  16. pobject=pool->Malloc(sizeof(T));  
  17.         new(pobject) ObjectType();  
  18. <ObjectType*>(pobject);  
  19.     }  
  20.     static void Delete(StaticMemory* pool, ObjectType* pobject)  
  21.     {  
  22. >~ObjectType();  
  23. >Free(pobject);  
  24.     }  
  25. };  
  26. #endif  


测试结果:

分单线程和多线程进行测试,重复的内存分配与释放在实际使用中是不太可能的,为了模拟实际使用,通过随机数来确定分配内存大小,同时也通过随机数来确定分配与释放操作。在测试过程中限制最大分配大小为1024,目的是为了测试小内存块的分配情况对比。

内存池单线程测试结果

分配与释放次数

malloc/free

内存池

                                                        100,000

            0.01s

        0.01s

                                                      1,000,000

            0.15s

        0.11s

                                                     10,000,000

            1.26s

        0.60s

                                                    100,000,000

            9.21s

        5.99s

                                                  1,000,000,000

            92.70s

        61.46s

内存池多线程测试结果

   线程数目

                malloc/free

                      内存池

1/1,000,000

                  0.15s

                      0.10s

2/1,000,000

                 1.49s

                      0.73s

4/1,000,000

                 9.945s

                      6.71s

8/1,000,000

                 45.60s

                     28.82s

进行多线程测试主要是测试多线程运行下,加锁给内存分配带来的影响,因此为了排除CPU的影响,测试采用的机器为16盒,16G内存的Linux服务器。

具体配置如下:

Intel(R) Xeon(R) CPU           E5630  @ 2.53GHz


stepping        : 2


cpu MHz         : 2527.084


cache size      : 12288 KB