C++内存池实现


利用C/C++开发大型应用程序中,内存的管理与分配是一个需要认真考虑的部分。本文描述了内存池设计原理并给出内存池的实现代码,代码支持Windows和Linux,多线程安全。内存池设计过程中需要考虑好内存的分配与释放问题,其实也就是空间和时间的矛盾。有的内存池设计得很巧妙,内存分配与需求相当,但是会浪费过多的时间去查找分配与释放,这就得不偿失;实际使用中,我们更多的是关心内存分配的速度,而不是内存的使用效率。基于此,本文按照如下思想设计实现内存池。主要包含三个结构:StiaticMemory, MemoryChunk和MemoryBlock,三者之间的关系如下图所示:

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操作,同时减少了内存碎片的增加。但是该设计中没有去验证释放的块冗余头部的正确性,因此故意释放不属于内存池中的块或者修改头部信息都会导致内存池操作失败,当然这些可以由程序员来控制。此外,内存池中分配出去的内存块如果不主动释放,内存池没有保留信息,不会自动释放,但是在退出的时候会验证验证是否完全释放,其实这个在系统测试时候就可以检测出来,我想这个缺陷也是可以弥补的,在此提出,希望使用者注意。

MemoryChunk.h 文件,线程安全

#ifndef MEMORY_CHUNK_H 
    #define MEMORY_CHUNK_H 
    #include <cstdio> 
    #include <cassert> 
    #include <cstdlib> 
     
    #ifdef WIN32 
    #include <windows.h> 
    typedef CRITICAL_SECTION MUTEXTYPE; 
    #define INITMUTEX(hMutex) InitializeCriticalSection(&hMutex) 
    #define DELMUTEX(hMutex) DeleteCriticalSection(&hMutex) 
    #define LOCK(hMutex) EnterCriticalSection(&hMutex) 
    #define UNLOCK(hMutex) LeaveCriticalSection(&hMutex) 
    #else 
    #include <pthread.h> 
    typedef pthread_mutex_t MUTEXTYPE; 
    #define INITMUTEX(hMutex) pthread_mutex_init(&hMutex,NULL) 
    #define DELMUTEX(hMutex) pthread_mutex_destroy(&hMutex) 
    #define LOCK(hMutex) pthread_mutex_lock(&hMutex) 
    #define UNLOCK(hMutex) pthread_mutex_unlock(&hMutex) 
    #endif 
     
    class MemoryChunk; 
     
    /** @struct MemoryBlock 
    * 
    */ 
    struct BlockHeader 
    { 
        MemoryChunk* pChunk; 
        size_t len; 
    }; 
    struct MemoryBlock; 
    struct BlockData 
    { 
        union{ 
            MemoryBlock* pNext; 
            char pBuffer; 
        }; 
    }; 
    struct MemoryBlock 
    { 
        BlockHeader header; 
        BlockData data; 
    }; 
     
    /** @class MemoryChunk 
    * 
    */ 
     
    class MemoryChunk 
    { 
    public: 
        MemoryChunk(size_t size, int count) 
        { 
            INITMUTEX(hMutex); 
            this->pFreeList=NULL; 
            this->size=size; 
            this->count=0; 
            MemoryBlock* pBlock; 
            while(count--){ 
                pBlock=CreateBlock(); 
                if(!pBlock)break; 
                pBlock->data.pNext=pFreeList; 
                pFreeList=pBlock; 
            } 
        } 
        ~MemoryChunk() 
        { 
            int tempcount=0; 
            MemoryBlock* pBlock; 
            while(pBlock=pFreeList){ 
                pFreeList=pBlock->data.pNext; 
                DeleteBlock(pBlock); 
                ++tempcount; 
            } 
            assert(tempcount==count);//!确保释放完全 
            DELMUTEX(hMutex); 
        } 
        void* malloc() 
        { 
            MemoryBlock* pBlock; 
            LOCK(hMutex); 
            if(pFreeList){ 
                pBlock=pFreeList; 
                pFreeList=pBlock->data.pNext; 
            } 
            else{ 
                if(!(pBlock=CreateBlock())){ 
                    UNLOCK(hMutex); 
                    return NULL; 
                } 
            } 
            UNLOCK(hMutex); 
            return &pBlock->data.pBuffer; 
        } 
        static void free(void* pMem) 
        { 
            MemoryBlock* pBlock=(MemoryBlock*)((char*)pMem-sizeof(BlockHeader)); 
            pBlock->header.pChunk->free(pBlock); 
        } 
        void free(MemoryBlock* pBlock) 
        { 
            LOCK(hMutex); 
            pBlock->data.pNext=pFreeList; 
            pFreeList=pBlock; 
            UNLOCK(hMutex); 
        } 
         
        MemoryChunk* Next(){return pNext;} 
     
    protected: 
        MemoryBlock* CreateBlock() 
        { 
            MemoryBlock* pBlock=(MemoryBlock*)::malloc(sizeof(BlockHeader)+size); 
     
            if(pBlock){ 
     
                pBlock->header.pChunk=this; 
                pBlock->header.len=size; 
                 
                ++count; 
            } 
            return pBlock; 
        } 
        void DeleteBlock(MemoryBlock* pBlock) 
        { 
            ::free(pBlock); 
        } 
    private: 
        MemoryBlock* pFreeList; 
        size_t size;//!Block大小 
        int count;//!Block数目 
        MemoryChunk* pNext; 
        MUTEXTYPE hMutex; 
    }; 
    #endif

StaticMemory.h文件,内存池对象

#ifndef STATIC_MEMORY_H 
    #define STATIC_MEMORY_H 
    #include "MemoryChunk.h" 
    /** @ StaticMemory.h 
    * 定义实现内存池 
    * 采用固定大小策略进行内存管理与分配 
    * 减少因大量小内存分配导致的内存碎片增加 
    */ 
    struct HeapHeader 
    { 
        size_t size; 
    }; 
    struct MemoryHeap 
    { 
        HeapHeader header; 
        char pBuffer; 
    }; 
     
    class StaticMemory 
    { 
    public: 
        typedef enum{MAX_SIZE=1024,MIN_SIZE=sizeof(MemoryChunk*)}; 
        StaticMemory() 
        { 
            chunkcount=0; 
            for(size_t size=MIN_SIZE; size<=MAX_SIZE; size*=2)++chunkcount; 
            //pChunkList=(MemoryChunk**)malloc(sizeof(MemoryChunk*)*chunkcount); 
            pChunkList=new MemoryChunk*[chunkcount]; 
            int index=0; 
            for(size_t size=MIN_SIZE; size<=MAX_SIZE; size*=2) 
            { 
                pChunkList[index++]=new MemoryChunk(size,1000); 
            } 
        } 
        ~StaticMemory() 
        { 
            for(int index=0; index<chunkcount; ++index) 
            { 
                delete pChunkList[index]; 
            } 
            //free(pChunkList); 
            delete[] pChunkList; 
        } 
        void* Malloc(size_t size) 
        { 
            if(size>MAX_SIZE){ 
                return malloc(size); 
            } 
            int index=0; 
            for(size_t tsize=MIN_SIZE; tsize<=MAX_SIZE; tsize*=2){ 
                if(tsize>=size)break; 
                ++index; 
            } 
            return pChunkList[index]->malloc(); 
        } 
        void Free(void* pMem) 
        { 
            if(!free(pMem))MemoryChunk::free(pMem); 
        } 
    protected: 
        void* malloc(size_t size) 
        { 
            MemoryHeap* pHeap=(MemoryHeap*)::malloc(sizeof(HeapHeader)+size); 
            if(pHeap){ 
                pHeap->header.size=size; 
                return &pHeap->pBuffer; 
            } 
            return NULL; 
        } 
        bool free(void* pMem) 
        { 
            MemoryHeap* pHeap=(MemoryHeap*)((char*)pMem-sizeof(HeapHeader)); 
            if(pHeap->header.size>MAX_SIZE){ 
                ::free(pHeap); 
                return true; 
            } 
            return false; 
        } 
    private: 
        MemoryChunk** pChunkList; 
        int chunkcount; 
    }; 
    #endif

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

#ifndef OBJECT_MANAGER_H 
#define OBJECT_MANAGER_H 
#include "StaticMemory.h" 
/** @class ObjectManager 
 * 实现利用内存池创建对象 
 * 要求对象具有缺省构造函数 
 */ 
template<typename T> 
class ObjectManager 

public: 
    typedef T ObjectType; 
 
    static ObjectType* Create(StaticMemory* pool) 
    { 
        void* pobject=pool->Malloc(sizeof(T)); 
        new(pobject) ObjectType(); 
        return static_cast<ObjectType*>(pobject); 
    } 
    static void Delete(StaticMemory* pool, ObjectType* pobject) 
    { 
        pobject->~ObjectType(); 
        pool->Free(pobject); 
    } 
}; 
#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

------------------------------分割线------------------------------

C++ Primer Plus 第6版 中文版 清晰有书签PDF+源代码

读C++ Primer 之构造函数陷阱

读C++ Primer 之智能指针

读C++ Primer 之句柄类

将C语言梳理一下,分布在以下10个章节中:

  1. Linux-C成长之路(一):Linux下C编程概要
  2. Linux-C成长之路(二):基本数据类型
  3. Linux-C成长之路(三):基本IO函数操作
  4. Linux-C成长之路(四):运算符
  5. Linux-C成长之路(五):控制流
  6. Linux-C成长之路(六):函数要义
  7. Linux-C成长之路(七):数组与指针
  8. Linux-C成长之路(八):存储类,动态内存
  9. Linux-C成长之路(九):复合数据类型
  10. Linux-C成长之路(十):其他高级议题

本文永久更新链接地址:

相关内容

    暂无相关文章