在我工作的项目里,有涉及到内存管理方面的内容。我对此产生了兴趣,就花时间研究了一下。
项目中内存管理类的工作原理如下:
该类共维护两个内存节点链表,一是空闲内存节点链表(以下称空闲链表),二是正在使用的内存节点链表(以下称忙碌链表)。同时还有一个动态数组,存放内存分配过程中产生的碎片的地址信息。程序一开始,先一次性申请好大概所需的内存,为这块内存加上节点头信息后,随即加入空闲链表中等待分配。分配过程:首先合并内存碎片(根据动态数组中保存的碎片信息,将碎片合并到相邻的节点中去),程序从遍历空闲链表,找到节点大小足够分配的节点,随后将需要的内存加上内存头信息取出,再整理空闲链表。如果原节点剩下的内存大于节点头结构体的大小,即可以成为新的节点,就取代原节点在空闲链表中的位置,否则就视为内存碎片,并将其地址及大小信息保存在动态数组中。如果遍历结束发现没有空闲节点可供分配,程序就new一块内存交给用户。分配结束还要将节点加入忙碌链表中去。释放过程:如果是new来的内存,就调用delete释放。如果是内存池自己分配的内存,就将其加入空闲链表中等待下一次的分配。经过上网搜索资料,我了解到这种预先申请好大片内存,然后自己分配管理的方式就叫做内存池技术,内存池又分为分配固定大小空间的内存池和分配可变大小空间的内存池。上述内存管理类实现的就是一个分配可变大小空间的内存池,但是进过我的测试,发现上述内存管类对内存分配回收的效率要比用new/delete分配低很多。内存池技术是为了解决由频繁申请释放内存带来的过大的时间上的开销以及可能因此产生大量内存碎片这两个问题的。上述内存池,虽可解决内存碎片问题,但是每次分配前需要合并内存碎片,消耗了大量时间,我觉得效果并不太好。
继续搜索资料,我看到了介绍分配固定大小内存池的文章以及介绍关于Apache内存池的文章,我很受启发,可以事先多申请几个内存块用于分配不同大小的内存,而每个内存块分配的大小是固定的。这样既可以分配不同大小的内存又可以避免内存碎片的回收问题,效率应该可以得到很大的提高。想好以后,我便开始着手实践。我做的这个内存池的效果如如下:
池中共有12个内存块分别用来分配8B,16B,32B……16KB大小的内存空间。每块的大小也从4KB,8KB,16KB……8MB不等。如果这些块的空间不够分配,内存池将会再申请一块内存与上一块形成链表结构。而大于16KB的内存,则交由new来申请。释放过程中,除了内存池一开始申请的12块意外,其余的块在全部处于空闲状态的时候会被delete掉,以减少内存池的大小。
源码如下:
MemPool.h
//分配单元依次为8B, 16B, 32B...16K, 以倍数增加, 大于16K的内存, 交由new/delete分配
//linux以4KB为限分页, 每块的大小为4KB的整数倍, 最小为4K(8B), 最大8M(16K)
#ifndef__MEMPOOL_H__
#define__MEMPOOL_H__
#include<iostream>
usingnamespace std;
#defineKB 1024
#defineBLOCK_NUM 12
#defineMEM_STATIC 0
#defineMEM_DYNAMIC 1
#defineSIZE_ALIGN(size, boundary) (((size)+ ((boundary) - 1)) & ~((boundary) - 1))//取大于size的boundary 的最小倍数,boundary 为2的整数次幂
typedefstruct STRU_MEM_NODE
{
STRU_MEM_NODE *pNextNode; //下一个内存节点
unsigned int iNodeSize; //节点的大小
char cType; //0:静态分配内存, 1:动态分配内存
}*PTR_MEM_NODE;
typedefstruct STRU_MEM_BLOCK
{
STRU_MEM_BLOCK *pNext; //指向下一个内存块(内存单元大小相同)
unsigned int iBlockSize; //内存块的总大小
PTR_MEM_NODE pFreeList; //空闲内存节点链表
unsigned int iOffset; //块中可分配的内存的偏移量
char pData[1]; //块中内存区的首地址
}*PTR_MEM_BLOCK;
classCMemPool
{
public:
~CMemPool();
bool InitMemPool(unsigned int MinBlock);
static CMemPool &GetInstance();
void *AllocMem(unsigned int size);
void FreeMem(void *pMem);
private:
CMemPool();
CMemPool(const CMemPool &);
void FreeBlock(PTR_MEM_BLOCK &pBlock);
void SizeAlign(unsigned int &size, intboundary);
//成员变量
private:
PTR_MEM_BLOCK pBlock[BLOCK_NUM];
unsigned int iMinBlock; //最小块的大小, 4K的整数倍
unsigned int iMinUnit; //最小分配单元的大小, 2的整数次幂
unsigned int iMaxUnit; //最大分配单元的大小, 2的整数次幂
};
#endif
MemPool.cpp
#include"MemPool.h"
CMemPool::CMemPool():iMinBlock(1),iMinUnit(3)
{
for (int i = 0; i < BLOCK_NUM; ++i)
{
pBlock[i] = NULL;
}
iMaxUnit = BLOCK_NUM + iMinUnit - 1;
}
CMemPool::CMemPool(constCMemPool &)
{
}
CMemPool::~CMemPool()
{
//释放所有内存块
for (int i = 0; i < BLOCK_NUM; ++i)
{
FreeBlock(pBlock[i]);
}
}
voidCMemPool::FreeBlock(PTR_MEM_BLOCK &pBlock)
{
if (pBlock != NULL)
{
FreeBlock(pBlock->pNext);
delete[] pBlock;
pBlock = NULL;
}
}
CMemPool&CMemPool::GetInstance()
{
static CMemPool MemPool;
return MemPool;
}
//2的整数次幂
unsignedint PowOf2(unsigned int n)
{
if (n == 0)
{
return 1;
}
else
{
return 2*PowOf2(n-1);
}
}
//初始化内存池
boolCMemPool::InitMemPool(unsigned int MinBlock)
{
iMinBlock = MinBlock;
//为每个内存块分配空间
for (int i = 0; i < BLOCK_NUM; ++i)
{
unsigned int size =iMinBlock*4*KB*PowOf2(i);
char *p = new char[size];
if (p == NULL)
{
return false;
}
pBlock[i] =reinterpret_cast<PTR_MEM_BLOCK>(p);
pBlock[i]->iBlockSize = size;
pBlock[i]->iOffset = 0;
pBlock[i]->pFreeList = NULL;
pBlock[i]->pNext= NULL;
}
return true;
}
//取一个32位二进制数中1的个数 原理:每2位中1的个数,每4位中1的个数,每8位1的个数...
intGetNumOfOneInBinary(unsigned int n)
{
n = (n & 0x55555555) + ((n >> 1)& 0x55555555);
n = (n & 0x33333333) + ((n >> 2)& 0x33333333);
n = (n & 0x0F0F0F0F)+ ((n >> 4) & 0x0F0F0F0F);
n = (n & 0x00FF00FF) + ((n >> 8)& 0x00FF00FF);
n = (n & 0x0000FFFF) + ((n >> 16)& 0x0000FFFF);
return n;
}
voidCMemPool::SizeAlign(unsigned int &size, int boundary)
{
//大于最大分配单元由new分配
if (size <= PowOf2(iMaxUnit))
{
size = SIZE_ALIGN(size, PowOf2(boundary));
//如果size不为2的整数次幂(即换算成二进制时1的个数不为1), 需要再次向上取整
if (GetNumOfOneInBinary(size) != 1)
{
SizeAlign(size, boundary+1);
}
}
}
void*CMemPool::AllocMem(unsigned int size)
{
char *pAlloc = NULL;
PTR_MEM_NODE pTmp = NULL;
if (size <= PowOf2(iMaxUnit)) //小于最大分配单元由内存池分配
{
//计算分配的内存大小
SizeAlign(size, iMinUnit);
//计算从哪一个块中分配内存
unsigned int index =GetNumOfOneInBinary(~(~(size-1)|size)) - iMinUnit;
if (index >= 0 && index <BLOCK_NUM)
{
PTR_MEM_BLOCK pCurBlock =pBlock[index];
if (pCurBlock != NULL)
{
//遍历分配该大小内存单元的链表,有空闲的块就分配,没有就重新申请一块用于分配
do
{
if(pCurBlock->pFreeList != NULL) //空闲链表中有节点可供分配
{
//分配内存, 更新pFreeList链表
pTmp =pCurBlock->pFreeList;
pCurBlock->pFreeList= pTmp->pNextNode;
pTmp->pNextNode =NULL;
pAlloc =reinterpret_cast<char *>(pTmp);
returnreinterpret_cast<void *>(pAlloc + sizeof(STRU_MEM_NODE));
}
else //空闲链表为空,从该块中分配节点
{
unsigned int LastMem= pCurBlock->iBlockSize - ((pCurBlock->pData + pCurBlock->iOffset) -reinterpret_cast<char *>(pCurBlock));
if (LastMem >=(size + sizeof(STRU_MEM_NODE))) //如果该内存块的剩余内存足够分配一个节点,才分配
{
pAlloc =pCurBlock->pData + pCurBlock->iOffset;
pTmp =reinterpret_cast<PTR_MEM_NODE>(pAlloc);
pTmp->cType= MEM_STATIC;
pTmp->iNodeSize= size + sizeof(STRU_MEM_NODE);
pTmp->pNextNode= NULL;
pCurBlock->iOffset+= (size + sizeof(STRU_MEM_NODE));
returnreinterpret_cast<void *>(pAlloc + sizeof(STRU_MEM_NODE));
}
}
} while ((pCurBlock =pCurBlock->pNext) != NULL);
//重新申请一个内存块
pAlloc = newchar[pBlock[index]->iBlockSize];
if (pAlloc == NULL)
{
return NULL;
}
else
{
PTR_MEM_BLOCK pNewBlock =reinterpret_cast<PTR_MEM_BLOCK>(pAlloc);
pTmp =reinterpret_cast<PTR_MEM_NODE>(pNewBlock->pData);
pTmp->cType =MEM_STATIC;
pTmp->iNodeSize = size+ sizeof(STRU_MEM_NODE);
pTmp->pNextNode =NULL;
pNewBlock->iBlockSize= pBlock[index]->iBlockSize;
pNewBlock->iOffset =pTmp->iNodeSize;
pNewBlock->pFreeList =NULL;
pNewBlock->pNext =pBlock[index]->pNext;
pBlock[index]->pNext =pNewBlock;
pAlloc =reinterpret_cast<char *>(pTmp);
returnreinterpret_cast<void *>(pAlloc + sizeof(STRU_MEM_NODE));
}
}
else
{
//未初始化
return NULL;
}
}
else
{
//index越界
return NULL;
}
}
else
{
pAlloc = new char[size +sizeof(STRU_MEM_NODE)];
if (pAlloc == NULL)
{
return NULL;
}
pTmp =reinterpret_cast<PTR_MEM_NODE>(pAlloc);
pTmp->cType = MEM_DYNAMIC;
pTmp->iNodeSize = size +sizeof(STRU_MEM_NODE);
pTmp->pNextNode = NULL;
return reinterpret_cast<void*>(pAlloc + sizeof(STRU_MEM_NODE));
}
}
voidCMemPool::FreeMem(void *pMem)
{
PTR_MEM_NODE pTmp =reinterpret_cast<PTR_MEM_NODE>(reinterpret_cast<char *>(pMem) -sizeof(STRU_MEM_NODE));
if (pTmp->cType == MEM_DYNAMIC)
{
delete[] reinterpret_cast<char*>(pTmp);
pTmp = NULL;
}
else if (pTmp->cType == MEM_STATIC)
{
//计算该内存单元属于哪个内存块链表
unsigned int size = pTmp->iNodeSize- sizeof(STRU_MEM_NODE);
unsigned int index =GetNumOfOneInBinary(~(~(size-1)|size)) - iMinUnit;
//判断该内存单元属于哪个内存块
PTR_MEM_BLOCK pCurBlock =pBlock[index];
PTR_MEM_BLOCK pPreBlcok = NULL;
bool bFreed = false;
while (pCurBlock != NULL)
{
if (pCurBlock->pData <=reinterpret_cast<char *>(pTmp) &&
(reinterpret_cast<char*>(pTmp) + pTmp->iNodeSize) <= (reinterpret_cast<char*>(pCurBlock) + pCurBlock->iBlockSize)) //在该内存块的范围内
{
pTmp->pNextNode =pCurBlock->pFreeList;
pCurBlock->pFreeList =pTmp;
bFreed = true;
}
//如果该内存块全部处于空闲状态, 回收整个内存块
if (pCurBlock != pBlock[index]) //非block数组中的块才回收
{
unsigned int FreeMem = 0;
PTR_MEM_NODE pNode =pCurBlock->pFreeList;
while (pNode != NULL)
{
FreeMem +=pNode->iNodeSize;
pNode =pNode->pNextNode;
}
if (pCurBlock->iOffset ==FreeMem) //全部空闲
{
pPreBlcok->pNext =pCurBlock->pNext;
delete[]reinterpret_cast<char *>(pCurBlock);
pCurBlock = NULL;
}
}
if (bFreed)
{
break;
}
else
{
pPreBlcok = pCurBlock;
pCurBlock =pCurBlock->pNext;
}
}
}
else
{
return ;
}
}
此内存池只是实现了最基本的分配回收的功能,日后可以加上线程安全的代码以支持多线程操作。经过测试这个内存池在分配回收16KB以下空间时的效率比用new/delete来的要高效不少。