目录
感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接
🐒🐒🐒 个人主页
🥸🥸🥸 C语言
🐿️🐿️🐿️ C语言例题
🐣🐣🐣 python
🐓🐓🐓 数据结构C语言
🐔🐔🐔 C++
🐿️🐿️🐿️ 文章链接目录
🤠🤠🤠 高并发内存池项目
定长内存池设计
设计思路
申请一大块内存,用一个指针指向申请的内存,这里的难点是内存释放,因为我们申请的是一块很大的内存,而使用的时候都是将这一大块内存切成一小块一小块的用,而用完后释放我们不可以直接将他还给系统,因为释放的内存太小了,如果还给系统就会出现内存碎片的问题,所以我们是要将需要释放的内存管理起来,当他们都释放后再归还内存,这里的管理方式就是用一个链表将内存管理起来
由于是定长内存池,这里的定长就是切固定大小的内存
具体实现
定长内存池初始化
首先我们需要一个类
class Objectpool
{
};
由于是定长内存池,所以类里面一点要有一个指针指向申请的内存空间,为了方便切割内存我们使用char*_memory,因为char是可以精确到1个字节1个字节的切割比较方便,如果是int的话那么我们要切割的长度就是4的倍数
此外因为申请内存是固定的我们可以用非类型的模版参数template<size_t N>表示我们申请的内存是一个固定大小为N
当然我们也可以用template表示ObjectPool每次获取的对象都是一个T对象,T的大小是固定的,这里我们选择的是用template
template<class T>
class ObjectPool
{
char* _memory=nullptr;
};
我们还需要考虑内存回收
当我们需要切割内存时_memory需要往后移动
当切割后的内存用完后需要回收我们需要用一个链表将他们串起来
可是现在有一个问题我们应该怎么样才能将他们串起来,我们是否需要再定义一个结构体将他们链接起来呢?
其实是不需要的,我们可以直接在内存当中修改,将后面的内存块地址存到前一个内存块当中,但是这里有一个要求就是32位下的指针大小是4个字节,而64位下的指针大小是8个字节,也就意味着当我们要存一个内存块的空间时,32位下内存块至少要有4个字节,而64位则至少要有8个字节
在32位下对于一个内存块有100字节我们则需要将他的头4个字节进行修改保存下一块内存的地址
T*New()申请内存
申请内存我们需要先看_memory是否为空,如果_memory为空的话那么就需要申请内存
T* New()
{
if (_memory==nullptr)//剩余内存不够一个对象大小是重新开大块空间
{
_memory =(char*) malloc(128 * 1024);//128kb
if (_memory == nullptr)
{
throw std::bad_alloc();
}
}
T* obj = (T*)_memory;
_memory += sizeof(T);
return obj;
}
如果申请后_memory仍为空那么就抛异常,反之就让一个对象obj指向申请内存的空间
_memory += sizeof(T)就表示的切割空间,让_memory切割 sizeof(T)大小的空间
这里有一个问题就是_memory走到最后的时候,如果_memory继续+= sizeof(T)就会造成越界,因为申请的内存以及用完了,而_memory现在在访问的是别的空间
为了解决这个问题我们还需要有一个remainBytes去记录还剩多少空间,当remainBytes<sizeof(T)的时候就意味着剩下的空间以及不足以支持我们继续切割了,可能需要重新申请一块空间
为什么是可能需要重新申请一块空间呢,因为_freeList是回收释放的内存,如果_freeList中有回收了的内存,那么这些内存是可以继续重复利用的
所以一开始需要一个指针next保存_freeList前面记录下来的位置,然后让obj指向_freeList,再让_freeList=next
T* obj = nullptr;
if (_freeList)
{
void* next = *((void**))_freeList);
obj = (T*)_freeList;//因为obj是T*,所以_freeList也要强转
_freeList = next;
}
void* next = * ((void**))_freeList)这段代码会在下面部分解释
还有一个问题就是sizeof(T)大小问题,因为这是一个类模版,如果T是char类型的话他的内存是小于4个字节的,int类型的话也就刚好4个字节,但是在64位下是不够的,所以需要判断sizeof(T) < sizeof(void*) ,表示T的内存大小是否小于一个指针,如果小于的话就要更改切割内存的大小
最后我们还需要对我们创造的对象进行初始化
这里用定位new显示调用T的构造函数初始化new(obj)T
代码
T* New()
{
T* obj = nullptr;
if (_freeList)
{
void* next = *((void**))_freeList);
obj =(T*) _freeList;
_freeList = next;
}
else
{
if (_remainBytes < sizeof(T))//剩余内存不够一个对象大小是重新开大块空间
{
_remainBytes = 128 * 1024;
_memory = (char*)malloc(_remainBytes);//128kb
if (_memory == nullptr)
{
throw std::bad_alloc();
}
}
obj = (T*)_memory;
size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);//因为T是一个类模版,当T是char或int时可能会导致内存不足以存储一个地址
_memory += objSize;
_remainBytes -= objSize;
}
new(obj)T;//定位new显示调用T的构造函数初始化
return obj;
}
void Delete(T* obj)回收内存
在最开始的时候_freeList是指向的空
当第一次有空间回收时,我们要让空间的前4个字节存空地址,具体办法就是让传入的指针强转成int*,那么我们对int进行解引用将前4个字节更改为nullptr就可以实现了
但是这里有一个问题就是int只有4个字节,这种方法只适用于32位下,在64位下是不行的
当然我们可以用if语句判断一个指针的大小,然后再进行细分也是可以的
下面有更简单的方式就是将obj强转成void**,也就是二级指针(其他类型的二级指针也是可以的,主要用的就是二级指针转换成一级指针的空间大小是4/8个字节),然后我们对他解引用,*(void**)obj = nullptr
上面是解决第一次内存块回收的问题,下面是解决多次回收的问题
多个内存块回收我们选择用头插的方式串起来,因为尾插效率太低
首先还是要让新的内存块保存_freeList指向的地址,然后让_freeList指向新的内存块
void Delete(T* obj)
{
if (_freeList==nullptr)
{
_freeList = obj;
*(void**)obj = nullptr;
}
else
{
*(void**)obj = _freeList;
_freeList = obj;
}
}
我们发现上面代码中
*(void**)obj = _freeList;
_freeList = obj;
这段代码是适用于所有情况的
一开始_freeList=nullptr
让obj前4/8个字节保存_freeList
然后让_freeList指向obj
即使有新回收的也是适用的
代码
void Delete(T* obj)
{
obj->~T();//显示调用析构函数
*(void**)obj = _freeList;
_freeList = obj;
}
设计的总代码
template<class T>
class ObjectPool
{
public:
T* New()
{
T* obj = nullptr;
if (_freeList)
{
void* next = *((void**)_freeList);
obj =(T*) _freeList;
_freeList = next;
}
else
{
if (_remainBytes < sizeof(T))//剩余内存不够一个对象大小是重新开大块空间
{
_remainBytes = 128 * 1024;
_memory = (char*)malloc(_remainBytes);//128kb
if (_memory == nullptr)
{
throw std::bad_alloc();
}
}
obj = (T*)_memory;
size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
_memory += objSize;
_remainBytes -= objSize;
}
new(obj)T;
return obj;
}
void Delete(T* obj)
{
obj->~T();
*(void**)obj = _freeList;
_freeList = obj;
}
private:
char* _memory = nullptr;//指向大块内存的指针
void* _freeList = nullptr;//还回过程中链接的自由链表头指针
size_t _remainBytes = 0;//大块内存切割过程中的剩余字节数
};
测试代码
struct TreeNode
{
int _val;
TreeNode* _left;
TreeNode* _right;
TreeNode()
:_val(0)
, _left(nullptr)
, _right(nullptr)
{}
};
void TestObjectPool()
{
// 申请释放的轮次
const size_t Rounds = 5;
// 每轮申请释放多少次
const size_t N = 100000;
std::vector<TreeNode*> v1;
v1.reserve(N);
size_t begin1 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v1.push_back(new TreeNode);
}
for (int i = 0; i < N; ++i)
{
delete v1[i];
}
v1.clear();
}
size_t end1 = clock();
std::vector<TreeNode*> v2;
v2.reserve(N);
ObjectPool<TreeNode> TNPool;
size_t begin2 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v2.push_back(TNPool.New());
}
for (int i = 0; i < N; ++i)
{
TNPool.Delete(v2[i]);
}
v2.clear();
}
size_t end2 = clock();
cout << "new cost time:" << end1 - begin1 << endl;
cout << "object pool cost time:" << end2 - begin2 << endl;
}
Objectpool.h文件代码
#include<iostream>
#include<vector>
#include<time.h>
using std::cout;
using std::endl;
template<class T>
class ObjectPool
{
public:
T* New()
{
T* obj = nullptr;
if (_freeList)
{
void* next = *((void**)_freeList);
obj =(T*) _freeList;
_freeList = next;
}
else
{
if (_remainBytes < sizeof(T))//剩余内存不够一个对象大小是重新开大块空间
{
_remainBytes = 128 * 1024;
_memory = (char*)malloc(_remainBytes);//128kb
if (_memory == nullptr)
{
throw std::bad_alloc();
}
}
obj = (T*)_memory;
size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
_memory += objSize;
_remainBytes -= objSize;
}
new(obj)T;
return obj;
}
void Delete(T* obj)
{
obj->~T();
*(void**)obj = _freeList;
_freeList = obj;
}
private:
char* _memory = nullptr;//指向大块内存的指针
void* _freeList = nullptr;//还回过程中链接的自由链表头指针
size_t _remainBytes = 0;//大块内存切割过程中的剩余字节数
};
struct TreeNode
{
int _val;
TreeNode* _left;
TreeNode* _right;
TreeNode()
:_val(0)
, _left(nullptr)
, _right(nullptr)
{}
};
void TestObjectPool()
{
// 申请释放的轮次
const size_t Rounds = 5;
// 每轮申请释放多少次
const size_t N = 100000;
std::vector<TreeNode*> v1;
v1.reserve(N);
size_t begin1 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v1.push_back(new TreeNode);
}
for (int i = 0; i < N; ++i)
{
delete v1[i];
}
v1.clear();
}
size_t end1 = clock();
std::vector<TreeNode*> v2;
v2.reserve(N);
ObjectPool<TreeNode> TNPool;
size_t begin2 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v2.push_back(TNPool.New());
}
for (int i = 0; i < N; ++i)
{
TNPool.Delete(v2[i]);
}
v2.clear();
}
size_t end2 = clock();
cout << "new cost time:" << end1 - begin1 << endl;
cout << "object pool cost time:" << end2 - begin2 << endl;
}
test.cpp文件代码
#include"Objectpool.h"
int main()
{
TestObjectPool();
return 0;
}