简单内存池原理及实现
程序设计中,需要经常申请释放内存,例如使用new/delete、malloc/free,系统会根据 “最优匹配“ 或 ”最先匹配“ 或其他算法在内存空闲块中查找一块空闲内存。当申请、释放较为频繁时,系统可能会需要合并内存块,产生额外开销;同时,频繁的申请释放可能造成系统当中产生大量的内存碎片,降低程序运行效率;使用不当时还会造成内存泄露。
一种较为有效的方式是程序向系统一次性申请一块较大的连续内存块,自行管理,自己进行内存的分配和释放。
内存池(memory pool)是代替直接调用malloc/free、new/delete进行内存管理的常用方法,当我们申请内存空间时,首先到我们的内存池中查找合适的内存块,而不是直接向操作系统申请,优势在于:
- 比malloc/free进行内存申请/释放的方式快
- 不会产生或很少产生堆碎片
- 可避免内存泄漏
这里实现一种简单有效的内存池。
- 维护一组内存池,重载new 、 delete 运算符,重载之后new、delete分配内存池中的内存,而不再是向系统申请和释放内存,内存池初始化时通过malloc、free向系统申请和释放。
- 创建一个MemeoryManager类,该类只有一个实例化对象,用于管理内存池。
- 创建MemoryAlloc类,即内存池类。一个内存池由若干个block块组成,Block块为最小内存单元。
- Block块可通过类或者结构体实现,记录当前块信息,例如:属于哪个内存池、编号、引用次数、下一个block块的位置等信息。
详细说明:
一个内存池MemoryAlloc包含若干个block块,空闲内存块通过链表的形式记录。维护一个头指针header指向头节点,当需要分配内存时,从头节点分配,并将头节点后移一位,释放内存时,将待释放的内存添加到链表头,并将header指针前移一位。block块同时记录当前块相关信息,例如,所属的内存池,编号信息,下一个块位置以及是否属于内存池(1. 当一次申请的内存过大时,没有合适的内存池可供分配,此时直接向操作系统申请内存,这样得到的内存即不属于内存池;2.当待分配内存池的所有块已被分配完毕时,当前内存池没有空闲块,此时也直接向操作系统申请内存,这一块内存也不属于内存池)等。初始化内存池时一个块分配的实际大小应为 block对象大小(描述信息头)+可用内存大小(例如一个包含若干个64kb内存块的内存池,每一个块初始化应当分配 sizeof(block)+64kb)。 通过MemoryMgr类对所有内存池进行管理,单例模式设计,只需一个实例化对象,通过数组映射的方式直接将不同大小的内存分配映射到对应内存池,无需查找,即当需要分配1-64kb内存时,直接映射到每一个块大小为64kb的内存池;当需要分配65kb-128kb内存时,直接映射到每一个块大小为128kb的内存池。初始化时,内存池大小及每个内存池中block块的大小及数量根据需要调整。
以下是源码
Alloc.h
#ifndef _ALLOC_H_
#define _ALLOC_H_
#include "MemoryMgr.hpp"
void* operator new(size_t size);
void* operator new[](size_t size);
void operator delete(void* p);
void operator delete[](void* p);
void* mem_alloc(size_t size);
void mem_free(void* p);
#endif
Alloc.cpp
#include"Alloc.h"
#include<stdlib.h>
void* operator new(size_t nSize)
{
return MemoryMgr::Instance().allocMem(nSize);
}
void* operator new[](size_t nSize)
{
return MemoryMgr::Instance().allocMem(nSize);
}
void operator delete(void* p)
{
MemoryMgr::Instance().freeMem(p);
}
void operator delete[](void* p)
{
MemoryMgr::Instance().freeMem(p);
}
void* mem_alloc(size_t size)
{
return malloc(size);
}
void mem_free(void* p)
{
free(p);
}
MemoryMgr.hpp
#ifndef _MemoryMgr_hpp_
#define _MemoryMgr_hpp_
#include<stdlib.h>
#include<assert.h>
#define MAX_MEMOTY_SIZE 1024
#ifdef _DEBUG //只在debug模式下打印输出信息
#include <stdio.h>
#define xprintf(...) printf(__VA_ARGS__)
#else
#define xprintf(...)
#endif
class MemoryAlloc;
//内存块 最小单元
class MemoryBlock
{
private:
MemoryBlock();
~MemoryBlock();
public:
int nID; //内存块编号
int nRef; //引用次数
MemoryAlloc* pAlloc; //所属内存块
MemoryBlock* pNext; //下一块位置
bool pPool; //是否在内存池中
//预留 对齐用 一个块大小为28字节
char cNULL[3];
};
//const int MemoryBlockSize = sizeof(MemoryBlock);
//内存池
class MemoryAlloc
{
public:
MemoryAlloc();
~MemoryAlloc();
//申请内存
void* allocMemory(size_t nsize);
//释放内存
void freeMemory(void* pMem);
//初始化内存池
void initMemory();
protected:
char* _pBuf; //内存池地址
MemoryBlock* _pHeader; //头部内存单元(可用)
size_t _nBlockSize; //内存单元大小
size_t _nBlockNum; //内存单元的数量
};
//方便初始化
template<size_t nBlockSize , size_t nBlockNum>
class MemoryAlloctor : public MemoryAlloc
{
public:
MemoryAlloctor()
{
size_t n = sizeof(void*);
_nBlockSize = (nBlockSize / n)*n + (nBlockSize%n ? n : 0); //字节对齐
_nBlockNum = nBlockNum;
this->initMemory();
}
};
//内存管理工具
class MemoryMgr
{
private:
MemoryMgr();
public:
static MemoryMgr& Instance()
{
//单例模式
static MemoryMgr mgr;
return mgr;
}
//申请内存
void* allocMem(size_t nSize);
//释放内存
void freeMem(void* pMem);
//内存池映射数组初始化
void init_szAlloc(int nBegin, int nEnd, MemoryAlloc* pMemA);
private:
MemoryAlloctor<64, 1000000> _mem64; //该内存池1000000个块,每个块大小为64kb
MemoryAlloctor<128, 1000000> _mem128;
MemoryAlloctor<256, 1000000> _mem256;
MemoryAlloctor<512, 1000000> _mem512;
MemoryAlloctor<1024, 1000000> _mem1024;
MemoryAlloc* _szAlloc[MAX_MEMOTY_SIZE + 1];
//数组用于将不同大小的内存分配直接映射到内存池,无需查找
};
#endif
MemoryMgr.cpp
#include "MemoryMgr.hpp"
MemoryAlloc::MemoryAlloc()
{
_pBuf = nullptr;
_pHeader = nullptr;
_nBlockSize = 0;
_nBlockNum = 0;
}
MemoryAlloc::~MemoryAlloc()
{
if (_pBuf) free(_pBuf);
}
void* MemoryAlloc::allocMemory(size_t nsize)
{
if (!_pBuf)
{
initMemory();
}
MemoryBlock* pReturn = nullptr;
if (!_pHeader) //头部为空 额外申请空间(当前内存池已被用完)
{
pReturn = (MemoryBlock*)malloc((nsize*sizeof(MemoryBlock)));
pReturn->nID = -1;
pReturn->pPool = false;
pReturn->nRef = 1;
pReturn->pAlloc = this;
pReturn->pNext = nullptr;
xprintf("MemoryAlloc :: allocMemery : %11x , id = %d , size = %d , external alloc , memory pool out ! \n", pReturn, pReturn->nID, nsize);
}
else
{
pReturn = _pHeader;
_pHeader = _pHeader->pNext;
assert(0 == pReturn->nRef);
pReturn->nRef = 1;
xprintf("MemoryAlloc :: allocMemery : %11x , id = %d , size = %d , internal alloc ! \n", pReturn, pReturn->nID, nsize);
}
return (char*)pReturn + sizeof(MemoryBlock);
}
void MemoryAlloc::freeMemory(void* pMem)
{
MemoryBlock* pBlock = (MemoryBlock*)((char*)pMem - sizeof(MemoryBlock));
assert(1 == pBlock->nRef);
if (--pBlock->nRef != 0) return;
if (pBlock->pPool)
{
pBlock->pNext = _pHeader;
_pHeader = pBlock;
xprintf("MemoryAlloc :: freeMemory : %11x , id = %d , internal free ! \n", pBlock, pBlock->nID);
}
else
{
free(pBlock);
}
return;
}
void MemoryAlloc::initMemory()
{
assert(nullptr == _pBuf);
if (_pBuf) return;
//从系统申请池内存
size_t realSize = _nBlockSize + sizeof(MemoryBlock);
size_t bufSize = realSize * _nBlockNum; //计算内存池的大小
_pBuf = (char*)malloc(bufSize); //向系统申请池内存
//初始化内存池
_pHeader = (MemoryBlock*)_pBuf;
_pHeader->pPool = true;
_pHeader->nID = 0;
_pHeader->nRef = 0;
_pHeader->pAlloc = this;
_pHeader->pNext = nullptr;
//遍历内存块进行初始化
MemoryBlock* temp = _pHeader;
for (int i = 1; i < _nBlockNum; ++i)
{
temp->pNext = (MemoryBlock*)((char*)temp + realSize);
temp = temp->pNext;
temp->pPool = true;
temp->nID = i;
temp->nRef = 0;
temp->pAlloc = this;
temp->pNext = nullptr;
}
}
MemoryMgr::MemoryMgr()
{
//初始化所有内存池
init_szAlloc(0, 64, &_mem64);
init_szAlloc(65, 128, &_mem128);
init_szAlloc(129, 256, &_mem256);
init_szAlloc(257, 512, &_mem512);
init_szAlloc(513, 1024, &_mem1024);
}
void* MemoryMgr::allocMem(size_t nSize)
{
if (nSize <= MAX_MEMOTY_SIZE)
{
return _szAlloc[nSize]->allocMemory(nSize);
}
else
{
MemoryBlock* pReturn = (MemoryBlock*)malloc(nSize + sizeof(MemoryBlock));
pReturn->pPool = false;
pReturn->nID = -1;
pReturn->nRef = 1;
pReturn->pAlloc = nullptr;
pReturn->pNext = nullptr;
xprintf("MemoryMgr :: allocMem : %11x , id = %d , size = %d , external alloc , Too much memory ! \n" , pReturn , pReturn->nID , nSize);
return ((char*)pReturn + sizeof(MemoryBlock));
}
}
void MemoryMgr::freeMem(void* pMem)
{
MemoryBlock* pBlock = (MemoryBlock*)((char*)pMem - sizeof(MemoryBlock));
if (pBlock->pPool)
{
pBlock->pAlloc->freeMemory(pMem);
}
else
{
if (--pBlock->nRef == 0)
{
xprintf("MemoryMgr :: freeMem : %11x , id = %d , external free ! \n", pBlock, pBlock->nID);
free(pBlock);
}
}
}
void MemoryMgr::init_szAlloc(int nBegin, int nEnd, MemoryAlloc* pMemA)
{
for (int i = nBegin; i <= nEnd; ++i)
{
_szAlloc[i] = pMemA;
}
}
main.cpp
#include<iostream>
#include<stdlib.h>
#include"Alloc.h"
using namespace std;
int main()
{
MemoryMgr::Instance();
char* data[1100];
for (size_t i = 0; i < 1100; i++)
{
data[i] = new char[1+i];
}
for (int i = 0; i < 1100; ++i)
{
delete data[i];
}
return 0;
}