博客主题:如何设计一个定长内存池
个人主页:https://blog.csdn.net/sobercq
CSDN专栏:https://blog.csdn.net/sobercq/category_12884309.html
Gitee链接:https://gitee.com/yunshan-ruo/high-concurrency-memory-pool
前言
上期我们说到malloc,我们知道malloc,在C/C++中动态申请内存都是通过malloc去申请内存, 并且malloc就是一个内存池。但是malloc因为要兼容通用,它什么场景下都可以用,但是什么场景下都可以用就意味着什么场景下都不会有很高的性能,所以我们要设计一个针对点一定场景下的内存池,来实现高性能。
所以本期我们就先设计一个定长内存池,先熟悉一下简单内存池是如何控制的,第二他会作为我们后面高并发内存池的一个基础组件。
那定长内存池就是针对固定大小内存块的申请和释放的内存池,由于定长内存池只需要支持固定大小内存块的申请和释放,因此我们可以将其性能做到极致,并且在实现定长内存池时不需要考虑内存碎片等问题,因为我们申请/释放的都是固定大小的内存块。
定长内存池
1.如何实现定长?
我们先创建和Test.cpp,ObjectPool.h,那如何实现定长,其一我们可以采用非类型模板参数来实现定长。
template<size_t N>
class ObjectPool
{
};
其二呢,我们也可以通过类型模板参数,采用这种方法设计我们也把它叫做对象池,即ObjectPool,在创建对象池时,对象池可以根据传入的对象类型的大小来实现“定长”,比如,创建定长内存池时传入的对象类型是内置类型,那么该内存池就只支持内置类型字节大小的内存块的申请和释放,如果是自定义类型,那我们的内存块大小就是sizeof(T)。
template<class T>
class ObjectPool
{
};
2.分配内存
首先,我们的内存池需要去堆上申请一块内存,这块内存假设我们已经申请下来了,我们要管理这块内存。那如何分配这大块内存呢?
如图所示,我们每次只需要在头部位置加对应大小的字节就可以了。
所以为了方便管理我们申请下来的大块内存,就需要用一个指针来管理。
//void* _memory
char* _memory
但是void这个类型没有意义,又不能加也不能减,还要强转,所以我们取最小的char,方便我们去切内存块,char指针+1也是+1字节。容易切分内存。
3.回收内存
那我们使用完这些划分后的内存块后,应该如何管理呢?我们可以用一个自由链表来管理内存,将回收来的内存块想象成一个链表的结点。
通过上图,我们能明确知道,这个对象池的构造了。
template <class T>
class ObjectPool
{
private:
char* _memory = nullptr; //管理系统申请的大块内存
void* _freelist = nullptr; //管理回收内存
};
3.如何New对象
我们既然已经知道我们的内存池的构造了,接下来就是如何去new一个对象。
刚才我们也说过,对象池,其大小是一个T的大小,所以我们可以先通过malloc去申请一块大点的空间,然后将其分配给对象。
T* New()
{
T* Obj = nullptr;
if (_memory == nullptr)
{
_memory = (char*)malloc(128 * 1024);//128K内存块
if (_memory == nullptr)
{
throw std::bad_alloc();//申请失败,抛异常
}
}
Obj = (T*)_memory;
_memory += sizeof(T);
return Obj;