c++实现的内存池

一、说明
1、通常我们申请内存/释放内存用的都是系统默认的函数new和delete,每次调用它们都会造成巨大的开销,申请/释放的次数不多可能看不出有什么不同。如果频繁的申请和释放内存,就会导致系统将大量的时间花在向堆申请、释放内存的操作中,在此情况下我们应该构建自己的内存池,以提高内存申请和释放的速度
2、内存池的原理就是一次申请一大块内存,然后在程序需要的时候从这一大块内存中分出一部分给程序用,释放内存的也不是真的向堆释放内存而是将之放回到内存池,以便下次使用
3、内存池的基本原理就是链表


二、结构
1、
template<class T>
struct Data
{
   T m_Data;
   Data* m_pNext;
}
这是实际用于存储对象的地方,定义为模板以适用于各种类型。由于Data*占4个字节(事实上指针在32位系统中都占用4个字节),为了提高利用效率,我建议T类型越大越好,这样内存的利用率才不会太低。假如T是char(一个字节),那么内存利用率才20%,可见并不是每种类型都适用这个内存池,我针对的是一些自定义类型或大数据类型


2、
class MemoryPool
{
}
这个就是内存池的定义,我们在这里边进行内存的释放和申请,为了使用方便,我将Data类型整合到MemoryPool中


template<class T>
class MemoryPool
{
   struct Data
   {
      T m_Data;
      Data* m_pNext;
   }
   Data*         m_pFreeList;//①
   unsigned long m_uSize;//②
   MPArray       m_AllBlock;//③ 
}


我们知道,每次申请内存的时候我们都是向系统申请一大块的内存(我喜欢称之为一个Block),然后将这一块内存建成链表,m_pFreeList指向表头,需要的时候我们可以从m_pFreeList表头弹出一个节点供程序使用


m_uSize用于保存该内存池总共有多少个内存节点


m_AllBlock用于保存各个大块的指针(MPArray是一个动态数组,类似于vector,当然功能没那么多,后面我会给出MPArray的实现):我们知道,每次申请内存的时候我们都是向系统申请一大块的内存,但是这一个Block也会有用完的时候,用完时我们必须再向系统申请一块内存,就这样我们可能会有Block1、Block2、Block3、…Blockn,m_AllBlock的作用就是用来保存它们(当然只是保存它们的指针)


3、
template<class T>
class MemoryPool
{
   struct Data
   {
      T m_Data;
      Data* m_pNext;
   }
   Data*         m_pFreeList;//①
   unsigned long m_uSize;//②
   MPArray       m_AllBlock;//③ 


public:
   MemoryPool();
   ~MemoryPool();


   T* New();
   void Delete(T* p);


   void Clear();


private:
   void Alloc();
   bool Find(T* p,T*& pre,T*& pCur,T*& pNext);
}
这是MemoryPool的完整声明,New是程序向内存池申请内存时调用的接口,Delete用于将不使用的内存还给内存池


Clear用于清理内存池,将内存池申请的所有内存释放


Alloc是内存池向系统申请内存的地方


Find是根据T*指针找到它所属的节点的位置,包括它的上一节点(可能没有)和下一个节点(可能没有)


三、在给出他的具体实现之前,我们先来分析一下:
1、最开始的时候m_pFreeList是空的,它不指向任何节点,因为还没有申请内存,m_uSize当然也是0


2、当程序调用内存池的New接口时,我们先判断m_pFreeList是否为空,这里我们分两种情况:
①、m_pFreeList为空,表示没有可分配的内存节点,这时需要调用Alloc向系统申请内存,内存准备完毕并且将链表建立(链表建立是在Allock中完成的)完成之后,m_pFreeList就不为空了,进行②
②、m_pFreeList不为空,将表头弹出给程序使用,然后m_pFreeList指向表头的下一位置


3、当程序向内存池归还内存(调用Delete)的时候,我们必须先判断这个内存是不是有内存池分配出去的,用Find函数在内存池中查找该指针指向的内存是否在内存池中,如果不是内存池分配的就不做任何处理,如果是内存池分配出去的我们就将这块内存加到m_pFreeList表头,以供下一次继续使用


4、Find函数是一个查找函数,实现可能有点复杂,主要是利用了边界的方式进行查找


5、注意:从内存池分配的内存绝对不要用delete或delete[]释放,要调用内存池接口Delete




template <class T>
class MemoryPool
{
        struct Data
        {
                T m_Data;//存放实际的数据
                Data* m_pNext;//下一节点,大小为4


                Data()
                {
                        m_pNext = NULL;        
                }
        };


        Data* m_pFreeList;//空闲内存链表
        unsigned long m_nSize;//内存块个数
        MPArray m_AllBlock;//存放每次分配的一大块内存的指针,可以用vector或什么其他的代替


public:


        MemoryPool()
        {
                m_pFreeList = NULL;
                m_nSize = 0;
                Alloc();
        }


        ~MemoryPool()
        {
                Clear();        
        }


        T* New()
        {
                if (m_pFreeList != NULL)
                {
                        Data* pData = m_pFreeList;
                        m_pFreeList = m_pFreeList->m_pNext;


                        return &pData->m_Data;
                }
                else
                {
                        Alloc();


                        Data* pData = m_pFreeList;
                        m_pFreeList = m_pFreeList->m_pNext;
                        return &pData->m_Data;
                }


                return NULL;
        }


        void Delete(T* p)
        {


                Data* pData = (Data*)p;


                
                bool bFind = false;


                bFind = Find(p);


                if (!bFind)
                {
                        return;
                }


                //加入到Free表中
                pData->m_pNext = NULL;


                pData->m_pNext = m_pFreeList;


                m_pFreeList = pData;
        }


        void Clear()
        {
                while(m_AllBlock.Empty())
                {
                        Data* pData = (Data*)m_AllBlock.Pop_front();


                        if (pData == NULL)
                        {
                                continue;
                        }


                        delete[] pData;
                }
        }


private:
        //实际内存分配的地方
        void Alloc()
        {
                Data* pData = new Data[LITTLE_BLOCK_NUM];
                int i = 0;
                int j = LITTLE_BLOCK_NUM-1;


                //建立链表,这样建立时间节约一半
                while(i < j)
                {
                        pData[i].m_pNext = &pData[i + 1];
                        pData[j-1].m_pNext = &pData[j];


                        ++i;
                        --j;
                }


                pData[LITTLE_BLOCK_NUM-1].m_pNext = m_pFreeList;
                m_pFreeList = pData;


                m_nSize += LITTLE_BLOCK_NUM;


                m_AllBlock.Add(pData);
        }        


        
        bool Find(T* p)
        {
                bool bFind = false;


                for (int i= 0; i < m_AllBlock.GetSize(); ++i)
                {
                        Data* pData = (Data*)m_AllBlock.Get(i);


                        if (pData == NULL)
                        {
                                continue;
                        }


                        //判断p的地址是否在这一大块的范围之内
                        //因为一大块的地址是连续的
                        if (p < &pData->m_Data || p > &pData[LITTLE_BLOCK_NUM-1].m_Data)//4096
                        {                        
                                continue;
                        }
                
                        bFind = true;


                        if (bFind)
                        {
                                break;
                        }


                }
                return bFind;
        }
};


四、以上就是这个内存池的完全实现,下面对他进行分析
1、LITTLE_BLOCK_NUM=4096,内存池每次向系统申请内存的时候固定申请4096*sizeof(Data)个字节的内存(也就是一个Block),即用new Data[LITTLE_BLOCK_NUM]的方式申请内存,这一块内存是连续的(即它们每个单元的地址都是相邻的),知道了这块内存的第一个地址,其他地址便会快速的计算出来
2、Data这个结构,我们必须将m_Data定义在m_pNext前面,只有这样编译器才会在构造Data的内存布局时将m_Data放在m_pNext的前面,所以m_Data的地址是和Data的地址重叠的,我们知道m_Data的地址的同时也就知道了Data的地址
3、将内存还给内存池的时候,调用Delete(T* p),我们需要知道p所指的这块内存是不是由内存池分配的,查找的时候有一个技巧:我们知道一个内存池由若干个Block构成,m_AllBlock保存着所有Block的第一个地址,而每一个Block在内存中是连续的,我们在比较的时候比较p和Block的边界,这样就可以知道这个p是不是在此Block内,如果p小于Block的下界或p大于Block的上界,就表示p不在这个Block之内,我们就可以查找下一个Block了,而不用比较Block的每一个元素。如果查完所有Block,p不在任何一个Block之内,就表示p不是由内存池分配的。如果能找到一个Block,让p处于它的下界和上界之间,那么就表明p所指的内存是由内存池分配的,并且可以马上找到p所属的节点,即:Data* pData = (Data*)p;就是它所属的节点的地址。然后我们就可以将这个节点加到空闲链表头。
4、Alloc分配内存的时候,一次分配4096个Data单元,这些单元式连续的,我们需要将这些单元连接成空闲链表,由于这些单元式连续的,我们可以用循环的方式调用pData[i].m_pNext = &pData[i+1];即可构造完成。




五、测试:
构造一个类用于测试
        class XT
        {
                char c[100];
                int ix[200];
        };
        typedef XT* PXT;


        unsigned long L = 1000000;
        PXT* psr = new PXT[L];


#define MEMPOOL//0.109


        MemoryPool<XT> mempool;


        clock_t start, mid,finish;   
        double  duration;       
        start = clock(); 


        for (unsigned long i= 0; i < L; ++i)
        {
#ifdef MEMPOOL 
        psr[i] = mempool.New();
#else
                psr[i] = new XT;
#endif
        }


        mid = clock();   
        duration = (double)(mid - start) / CLOCKS_PER_SEC;   
        printf( "new用时:%f seconds \n", duration ); 


        for (unsigned long i= 0; i < L; ++i)
        {
#ifdef MEMPOOL 
                 mempool.Delete(psr[i]);
#else
                delete psr[i];
#endif
        }


        finish = clock();   
        duration = (double)(finish - mid) / CLOCKS_PER_SEC;   
        printf( "delete用时:%f seconds \n", duration ); 
     
        for (unsigned long i= 0; i < L; ++i)
        {
#ifdef MEMPOOL 
                psr[i] = mempool.New();
#else
                psr[i] = new XT;
#endif
        }


        start = clock();   
        duration = (double)(start - finish) / CLOCKS_PER_SEC;   
        printf( "new用时:%f seconds \n", duration );


        for (unsigned long i= 0; i < L; ++i)
        {
#ifdef MEMPOOL 
                mempool.Delete(psr[i]);
#else
                delete psr[i];
#endif
        }


        finish = clock();   
        duration = (double)(finish - start) / CLOCKS_PER_SEC;   
        printf( "delete用时:%f seconds \n", duration );


        for (unsigned long i= 0; i < L; ++i)
        {
#ifdef MEMPOOL 
                psr[i] = mempool.New();
#else
                psr[i] = new XT;
#endif
        }


        start = clock();   
        duration = (double)(start - finish) / CLOCKS_PER_SEC;   
        printf( "new用时:%f seconds \n", duration );
        for (unsigned long i= 0; i < L; ++i)
        {
#ifdef MEMPOOL 
                mempool.Delete(psr[i]);
#else
                delete psr[i];
#endif
        }


        finish = clock();   
        duration = (double)(finish - start) / CLOCKS_PER_SEC;   
        printf( "delete用时:%f seconds \n", duration );



我们循环1000000次进行内存申请和释放,可以比较:通过内存池申请内存大概是new的四倍(这是建立在内存池还没有向系统申请内存的基础上,若内存池已经申请了大量内存,这个速度可以提高到20倍以上),而释放的速度,内存池的释放速度比较固定,都是6.6s左右;delete则有些变化,开始时12s多,后面通常是2s多


优缺点:
优点:内存申请极快,释放速度固定

缺点:内存释放的速度有待提高


附:MPArray的实现

class MPArray
{
MPArray& operator= (const MPArray& srcArray);
public:
MPArray(int iAutoFree = AUTO_FREE)
{
m_iPos = -1;
m_iSize = 0;
m_iAutoFree = iAutoFree;
m_pData = NULL;
}


MPArray(MPArray& srcArray,int iAutoFree = AUTO_FREE)
{
m_iPos = -1;
m_iSize = 0;
m_iAutoFree = iAutoFree;
m_pData = NULL;


Copy(srcArray);
}


~MPArray()
{
RemoveAll();


m_iSize = 0;
m_iPos = -1;
}




void SetAutoFree(int iAutoFree = 1)
{
m_iAutoFree = iAutoFree;
}


int GetSize()
{
return m_iSize;
}
int GetUpperBound()
{
}


void SetSize(int iSize)
{

void** pTemp = new void*[iSize];


int iTemp = 0;
if (m_iSize > iSize)
{
m_iPos = -1;
iTemp = iSize;
}
else
{
iTemp = m_iSize;
}


for (int i = 0; i < iTemp; ++i)
{
pTemp[i] = m_pData[i];
}


delete[] m_pData;


m_pData = pTemp;

m_iSize = iSize;
}


void FreeExtra();
void RemoveAll()
{
int i = 0;


if (AUTO_FREE == m_iAutoFree)
{
for (i = 0 ; i < m_iSize ;++i)
{
delete m_pData[i];
m_pData[i] = NULL;
}
}

delete[] m_pData;


m_pData = NULL;


m_iSize = 0;


m_iPos = -1;
}


void* Get(int iIndex)
{
if (m_iSize <= 0)
{
return NULL;
}


if (iIndex < 0)
{
return NULL;
}


return m_pData[iIndex];
}
void Set(int iIndex,void* pElement)
{
if (iIndex >= m_iSize)
{
SetSize(iIndex);
}


m_pData[iIndex] = pElement;
}


void* Element(int iIndex)
{
if (m_iSize <= 0)
{
return NULL;
}


if (iIndex < 0)
{
return NULL;
}


return m_pData[iIndex];
}


void** GetData()
{
return m_pData;
}


void Add(void* element)
{
SetSize(m_iSize + 1);
m_pData[m_iSize-1] = element;


}


void Append(MPArray& srcArray)
{
int iTemp = m_iSize;


SetSize(m_iSize + srcArray.GetSize());


for (int i = iTemp; i < m_iSize ; i++)
{
m_pData[i] = srcArray.Get(i-iTemp);
}
}


void Copy(MPArray& srcArray)
{
int iTemp = m_iSize;


SetSize(srcArray.GetSize());


for (int i = 0; i < m_iSize ; i++)
{
m_pData[i] = srcArray.Get(i);
}
}


void* operator [] (int iIndex)
{
return Get(iIndex);
}


void Insert(int iIndex,void* element,int iCount = 1)
{
if (iIndex < 0 || iCount < 1 || iIndex > m_iSize)
{
return;
}


int iTemp = m_iSize;


SetSize(m_iSize+iCount);


for (int i = m_iSize-1; i > iIndex; --i)
{
m_pData[i] = m_pData[i-iCount];
}


for (int i = iIndex ; i < iCount ; ++i)
{
m_pData[i] = element;
}
}
void Remove(int iIndex,int iCount = 1)
{
if (iIndex < 0 || iCount > m_iSize || iIndex >= m_iSize)
{
return;
}


int iTemp = m_iSize;

for (int i = iIndex; i < m_iSize-iCount; ++i)
{
m_pData[i] = m_pData[i+iCount];
}


m_iSize -= iCount;
}
void Insert(int iIndex,MPArray& srcArray)
{
if (iIndex < 0 || iIndex > m_iSize)
{
return;
}


int iTemp = srcArray.GetSize();


SetSize(m_iSize+srcArray.GetSize());


for(int i = m_iSize-1 ; i > iIndex + iTemp -1; --i)
{
m_pData[i] = m_pData[i-iTemp];
}


for (int i = iIndex; i < iIndex+ iTemp; ++i)
{
m_pData[i] = srcArray.Get(i-iIndex);
}
}


void Clear()
{
RemoveAll();
m_iPos = -1;
}


void* First()
{
return Get(0);
}
void* Last()
{
return Get(m_iSize-1);
}


void* Prev()
{
return Get(m_iPos-1);
}
void* Next()
{
return Get(m_iPos+1);
}


void* GetCurrentData()
{
return Get(m_iPos);
}


bool Empty()
{
if (m_iSize == 0)
{
return true;
}
return false;
}


void Push_back(void* pElement)
{
Add(pElement);
}


void Push_front(void* pElement)
{
Insert(0,pElement);
}


void* Pop_back()
{
if (m_iSize <= 0)
{
return NULL;
}
void* pData = Last();


Remove(GetSize()-1);


return pData;
}


void* Pop_front()
{
if (m_iSize <= 0)
{
return NULL;
}


void* pData = First();


Remove(0);


return pData;
}


private:
void** m_pData;
int m_iSize;
int m_iPos;
int m_iAutoFree;
};



















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值