在C/C++中内存的管理是非常头痛的事情,这里作者不再多解释,请参考这篇文章:https://blog.csdn.net/business122/article/details/80566230,作者也是参考这篇文章进行对内存池的改进和进化。
1、封装一个类用于管理内存池的使用如下,很容易看得懂,其实就是向内存池申请size个空间并进行构造,返回是首个元素的地址。释放也是一样,不过释放多个的时候需要确保这多个元素的内存是连续的。
#pragma once
#include <set>
template<typename T, typename Alloc = std::allocator<T>>
class AllocateManager
{
private:
typedef typename Alloc::template rebind<T>::other other_;
other_ m_allocate;//创建一个内存池管理器
public:
//MemoryPool申请空间
T * allocate(size_t size = 1)
{
//_SCL_SECURE_ALWAYS_VALIDATE(size != 0);
T * node = m_allocate.allocate(size);
m_allocate.construct(node, size);
return node;
}
//Allocator申请空间
T * allocateJJ(size_t size = 1)
{
//_SCL_SECURE_ALWAYS_VALIDATE(size != 0);
T * node = m_allocate.allocate(size);
m_allocate.construct(node);
return node;
}
//释放并回收空间
void destroy(T * node, size_t size = 1)
{
//_SCL_SECURE_ALWAYS_VALIDATE(size != 0);
for (int i = 0; i < size; i++)
{
m_allocate.destroy(node);
m_allocate.deallocate(node,1);
node++;
}
}
//获得当前内存池的大小
const size_t getMenorySize()
{
return m_allocate.getMenorySize();
}
//获得当前内存池的块数
const size_t getBlockSize()
{
return m_allocate.getBlockSize();
}
};
rebind的设计跟C++stl里的设计是同样套路,stl设计代码如下:
template<class _Elem,
class _Traits,
class _Ax>
class basic_string
: public _String_val<_Elem, _Ax>
{
...
typedef _String_val<_Elem, _Ax> _Mybase;
typedef typename _Mybase::_Alty _Alloc;
...
template<class _Ty,
class _Alloc>
class _String_val
: public _String_base
{
...
typedef typename _Alloc::template
rebind<_Ty>::other _Alty;
...
template<class _Ty>
class allocator
: public _Allocator_base<_Ty>
{
...
template<class _Other>
struct rebind
{ // convert an allocator<_Ty> to an allocator <_Other>
typedef allocator<_Other> other;
};
...
2、内存池设计代码,下面会一个一个方法抛开说明
#pragma once
#include <mutex>
template<typename T, int BlockSize = 6, int Block = sizeof(T) * BlockSize>
class MemoryPool
{
public:
template<typename F>
struct rebind
{
typedef MemoryPool<F, BlockSize> other;
};
MemoryPool()
{
m_FreeHeadSlot = nullptr;
m_headSlot = nullptr;
m_currentSlot = nullptr;
m_LaterSlot = nullptr;
m_MenorySize = 0;
m_BlockSize = 0;
}
~MemoryPool()
{
//将每一块内存delete
while (m_headSlot)
{
Slot_pointer pre = m_headSlot;
m_headSlot = m_headSlot->next;
operator delete(reinterpret_cast<void*>(pre));
}
}
//申请空间
T * allocateOne()
{
//空闲的位置有空间用空闲的位置
if (m_FreeHeadSlot)
{
Slot_pointer pre = m_FreeHeadSlot;
m_FreeHeadSlot = m_FreeHeadSlot->next;
return reinterpret_cast<T*>(pre);
}
//申请一块内存
if (m_currentSlot >= m_LaterSlot)
{
Char_pointer blockSize = reinterpret_cast<Char_pointer>(operator new(Block + sizeof(Slot_pointer)));
m_MenorySize += (Block + sizeof(Slot_pointer));
m_BlockSize++;
reinterpret_cast<Slot_pointer>(blockSize)->next = m_headSlot;//将新内存放在表头
m_headSlot = reinterpret_cast<Slot_pointer>(blockSize);
m_currentSlot = reinterpret_cast<Slot_pointer>(blockSize + sizeof(Slot_pointer));//跳过指向下一块的指针这段内存
m_LaterSlot = reinterpret_cast<Slot_pointer>(blockSize + Block + sizeof(Slot_pointer) - sizeof(Slot_)+1);//指向最后一个内存的开头位置
}
return reinterpret_cast<T*>(m_currentSlot++);
}
/*动态分配空间,注意:分配超过2个空间会在块里面创建占用4字节的空间存放数组的指针,
这个空间不会被回收,所以动态分配最好分配大空间才使用动态
*/
T * allocate(size_t size = 1)
{
std::unique_lock<std::mutex> lock{ this->m_lock };
//申请一个空间
if (size == 1)
return allocateOne();
Slot_pointer pReSult = nullptr;
/*先计算最后申请的块空间够不够,不适用回收的空间,因为回收空间不是连续*/
int canUseSize = reinterpret_cast<int>(m_LaterSlot) + sizeof(Slot_) - 1 - reinterpret_cast<int>(m_currentSlot);
int applySize = sizeof(T) * size + sizeof(T*);//创建数组对象时多了个指针,所以内存要加个指针的大小
if (applySize <= canUseSize) //空间足够,把剩余空间分配出去
{
pReSult = m_currentSlot;
m_currentSlot = reinterpret_cast<Slot_pointer>(reinterpret_cast<Char_pointer>(m_currentSlot) + applySize);
return reinterpret_cast<T*>(pReSult);
}
/*空间不够动态分配块大小,不把上一块剩余的空间使用是因为空间是需要连续,
所以上一块会继续往前推供下次使用*/
Char_pointer blockSize = reinterpret_cast<Char_pointer>(operator new(applySize + sizeof(Slot_pointer)));
m_MenorySize += (applySize + sizeof(Slot_pointer));
m_BlockSize++;
if (!m_headSlot)//目前没有一块内存情况
{
reinterpret_cast<Slot_pointer>(blockSize)->next = m_headSlot;
m_headSlot = reinterpret_cast<Slot_pointer>(blockSize);
m_currentSlot = reinterpret_cast<Slot_pointer>(blockSize + sizeof(Slot_pointer));
m_LaterSlot = reinterpret_cast<Slot_pointer>(blockSize + Block + sizeof(Slot_pointer) - sizeof(Slot_) + 1);
pReSult = m_currentSlot;
m_currentSlot = m_LaterSlot;//第一块内存且是动态分配,所以这一块内存是满的
}
else
{
//这个申请一块动态内存就用完,直接往头后面移动
Slot_pointer currentSlot = nullptr;
Slot_pointer next = m_headSlot->next;
currentSlot = reinterpret_cast<Slot_pointer>(blockSize);
currentSlot->next = next;
m_headSlot->next = currentSlot;
pReSult = reinterpret_cast<Slot_pointer>(blockSize + sizeof(Slot_pointer));//跳过指向下一块的指针这段内存
}
return reinterpret_cast<T*>(pReSult);
}
//使用空间
void construct(T * p, size_t size = 1)
{
//_SCL_SECURE_ALWAYS_VALIDATE(size != 0);
if (size == 1)
new (p)T();
else
new (p)T[size]();
}
//析构一个对象
void destroy(T * p)
{
p->~T();
}
//回收一个空间
void deallocate(T * p, size_t count = 1)
{
std::unique_lock<std::mutex> lock{ this->m_lock };
reinterpret_cast<Slot_pointer>(p)->next = m_FreeHeadSlot;
m_FreeHeadSlot = reinterpret_cast<Slot_pointer>(p);
}
const size_t getMenorySize()
{
return m_MenorySize;
}
const size_t getBlockSize()
{
return m_BlockSize;
}
private:
union Slot_
{
T _data;
Slot_ * next;
};
typedef Slot_* Slot_pointer;
typedef char* Char_pointer;
Slot_pointer m_FreeHeadSlot;//空闲的空间头部位置
Slot_pointer m_headSlot;//指向的头位置
Slot_pointer m_currentSlot;//当前所指向的位置
Slot_pointer m_LaterSlot;//指向最后一个元素的开始位置
size_t m_MenorySize;
size_t m_BlockSize;
// 同步
std::mutex m_lock;
static_assert(BlockSize > 0, "BlockSize can not zero");
};
3、申请一个空间,当回收的内存没有或内存块空间不够时,新开辟一块内存,并将新内存放在表头,返回新内存的头地址,如果内存块还有空间,那么返回首个空余的空间
//申请空间
T * allocateOne()
{
//空闲的位置有空间用空闲的位置
if (m_FreeHeadSlot)
{
Slot_pointer pre = m_FreeHeadSlot;
m_FreeHeadSlot = m_FreeHeadSlot->next;
return reinterpret_cast<T*>(pre);
}
//申请一块内存
if (m_currentSlot >= m_LaterSlot)
{
Char_pointer blockSize = reinterpret_cast<Char_pointer>(operator new(Block + sizeof(Slot_pointer)));
m_MenorySize += (Block + sizeof(Slot_pointer));
m_BlockSize++;
reinterpret_cast<Slot_pointer>(blockSize)->next = m_headSlot;//将新内存放在表头
m_headSlot = reinterpret_cast<Slot_pointer>(blockSize);
m_currentSlot = reinterpret_cast<Slot_pointer>(blockSize + sizeof(Slot_pointer));//跳过指向下一块的指针这段内存
m_LaterSlot = reinterpret_cast<Slot_pointer>(blockSize + Block + sizeof(Slot_pointer) - sizeof(Slot_)+1);//指向最后一个内存的开头位置
}
return reinterpret_cast<T*>(m_currentSlot++);
}
4、当分配超过2个元素空间时,先判断空闲块的空间够不够分配,够分配,不够新开辟一个大小跟申请元素个数一样的内存块,并将该块内存向表头置后,返回该快首地址。注意,由于分配多个元素的空间也就是分配一个数组,这个时候在下一步调用构造函数时会构造数组对象,数组对象会多一个指针空间指向该数组,所以申请n+1个元素时加上一个指针的空间,否则会泄漏。
这个指针的空间是没有用的,释放和回收空间是不会回收这个指针,这样它就会占用了内存块一个指针空间,就相当于磁盘分区有未分配的内存一样,分配多个元素空间时这个是无法避免的。
/*动态分配空间,注意:分配超过2个空间会在块里面创建占用4字节的空间存放数组的指针,
这个空间不会被回收,所以动态分配最好分配大空间才使用动态
*/
T * allocate(size_t size = 1)
{
std::unique_lock<std::mutex> lock{ this->m_lock };
//申请一个空间
if (size == 1)
return allocateOne();
Slot_pointer pReSult = nullptr;
/*先计算最后申请的块空间够不够,不适用回收的空间,因为回收空间不是连续*/
int canUseSize = reinterpret_cast<int>(m_LaterSlot) + sizeof(Slot_) - 1 - reinterpret_cast<int>(m_currentSlot);
int applySize = sizeof(T) * size + sizeof(T*);//创建数组对象时多了个指针,所以内存要加个指针的大小
if (applySize <= canUseSize) //空间足够,把剩余空间分配出去
{
pReSult = m_currentSlot;
m_currentSlot = reinterpret_cast<Slot_pointer>(reinterpret_cast<Char_pointer>(m_currentSlot) + applySize);
return reinterpret_cast<T*>(pReSult);
}
/*空间不够动态分配块大小,不把上一块剩余的空间使用是因为空间是需要连续,
所以上一块会继续往前推供下次使用*/
Char_pointer blockSize = reinterpret_cast<Char_pointer>(operator new(applySize + sizeof(Slot_pointer)));
m_MenorySize += (applySize + sizeof(Slot_pointer));
m_BlockSize++;
if (!m_headSlot)//目前没有一块内存情况
{
reinterpret_cast<Slot_pointer>(blockSize)->next = m_headSlot;
m_headSlot = reinterpret_cast<Slot_pointer>(blockSize);
m_currentSlot = reinterpret_cast<Slot_pointer>(blockSize + sizeof(Slot_pointer));
m_LaterSlot = reinterpret_cast<Slot_pointer>(blockSize + Block + sizeof(Slot_pointer) - sizeof(Slot_) + 1);
pReSult = m_currentSlot;
m_currentSlot = m_LaterSlot;//第一块内存且是动态分配,所以这一块内存是满的
}
else
{
//这个申请一块动态内存就用完,直接往头后面移动
Slot_pointer currentSlot = nullptr;
Slot_pointer next = m_headSlot->next;
currentSlot = reinterpret_cast<Slot_pointer>(blockSize);
currentSlot->next = next;
m_headSlot->next = currentSlot;
pReSult = reinterpret_cast<Slot_pointer>(blockSize + sizeof(Slot_pointer));//跳过指向下一块的指针这段内存
}
return reinterpret_cast<T*>(pReSult);
}
其他小的方法就不介绍了,代码也有注释很容易看得懂。
5、性能测试
动态分配时,num1表示分配块数,num2表示分配的每块大小。
逐步申请和释放一千万个空间(元素为单位),速度如下,C++是最慢的,MemoryPool快乐接近20倍,MemoryPool动态分配会更快乐些(面对疾风吧)。
测试代码:
#include <iostream>
#include <string>
#include <set>
#include <ctime>
#include <thread>
#include"MemoryPool.h"
#include"AllocateManager.h"
using namespace std;
//动态分配时,num1表示块数,num2表示每块大小
#define num1 1000
#define num2 10000
class Test
{
public:
int a;
~Test()
{
//cout << a << " ";
}
};
void TestByCjj()
{
clock_t start;
start = clock();
Test * p[num1][num2];
Test * t;
AllocateManager<Test, allocator<Test>> pool;
start = clock();
int count = 0;
//向内存池申请空间并构造出对象
for (int i = 0; i < num1; i++)
{
for (int j = 0; j < num2; j++)
{
t = pool.allocateJJ(1);
t->a = count++;
p[i][j] = t;
}
}
//根据对象从内存池释放并回收该空间
for (int i = 0; i < num1; i++)
{
for (int j = 0; j < num2; j++)
{
t = p[i][j];
pool.destroy(t);
}
}
std::cout << "C++ Time: ";
std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << endl;
}
void TestByOne()
{
clock_t start;
start = clock();
Test * p[num1][num2];
Test * t;
AllocateManager<Test, MemoryPool<Test, 1024>> memoryPool;
start = clock();
int count = 0;
for (int i = 0; i < num1; i++)
{
for (int j = 0; j < num2; j++)
{
t = memoryPool.allocate(1);
t->a = count++;
p[i][j] = t;
}
}
for (int i = 0; i < num1; i++)
{
for (int j = 0; j < num2; j++)
{
t = p[i][j];
memoryPool.destroy(t);
}
}
std::cout << "MemoryPool One Time: ";
std::cout << (((double)clock() - start) / CLOCKS_PER_SEC);
std::cout << " 内存块数量:" << memoryPool.getBlockSize();
std::cout << " 内存消耗(byte):" << memoryPool.getMenorySize() << std::endl;
}
void TestByBlock()
{
clock_t start;
start = clock();
Test * p[num1][num2];
Test * t;
AllocateManager<Test, MemoryPool<Test, 1024>> memoryPool;
start = clock();
int count = 0;
for (int i = 0; i < num1; i++)
{
t = memoryPool.allocate(num2);
for (int j = 0; j < num2; j++)
{
t->a = count++;
p[i][j] = t++;
}
}
for (int i = 0; i < num1; i++)
{
for (int j = 0; j < num2; j++)
{
Test * t = p[i][j];
memoryPool.destroy(t);
}
}
std::cout << "MemoryPool Block Time: ";
std::cout << (((double)clock() - start) / CLOCKS_PER_SEC);
std::cout << " 内存块数量:" << memoryPool.getBlockSize();
std::cout << " 内存消耗(byte):" << memoryPool.getMenorySize() << std::endl;
}
int main()
{
TestByCjj();
TestByOne();
TestByBlock();
return 0;
}
来源:CSDN
作者:HumorChess
链接:https://blog.csdn.net/qq_33700123/article/details/103651472