在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;
}