記憶體池——實現一個簡單的固定大小的記憶體池
最近在STL當中看到了第二級記憶體分配器,這裡有個記憶體池的內容,在這在知乎上看到了記憶體池的相關內容,所以萌生了一個想自己寫一個簡單的記憶體池的想法。
這種簡單的記憶體池,援引自知乎的:
實現固定記憶體分配器:
即實現一個 FreeList,每個 FreeList 用於分配固定大小的記憶體塊,比如用於分配 32位元組物件的固定記憶體分配器,之類的。每個固定記憶體分配器裡面有兩個連結串列,OpenList 用於儲存未分配的空閒物件,CloseList用於儲存已分配的記憶體物件,那麼所謂的分配就是從 OpenList 中取出一個物件放到 CloseList 裡並且返回給使用者,釋放又是從 CloseList 移回到 OpenList。分配時如果不夠,那麼就需要增長 OpenList:申請一個大一點的記憶體塊,切割成比如 64 個相同大小的物件新增到 OpenList中。這個固定記憶體分配器回收的時候,統一把先前向系統申請的記憶體塊全部還給系統。
1.探索記憶體池的使用原因
為什麼要使用記憶體池呢?這個解決方案也算是突出CPP的效率的一個方面了,我們會經常面對類似於高併發的這種情況,即簡單理解為經常性的new物件,然後delete物件,經常性的發出分配請求和釋放請求給作業系統,勢必帶來效率的下降,所以考慮到這一點,設計者們設計出了記憶體池。
記憶體池簡單的理解就是,進行一次性的開出大塊空間,當你需要構造物件的時候,拿出來構造,當你需要釋放的時候,並不還給作業系統,還給記憶體池,然後接下來使用直接從記憶體池當中取出進行操作。
所以,在多次進行分配請求與釋放請求的場景下,記憶體池能夠提高效率。
另外,當我們進行釋放以後,會產生內碎片的問題,就是對一塊整塊的記憶體,會出現中間出現片段,這樣,為了提升記憶體的利用率,我們把這快空間還回記憶體池,下次可以再使用這塊片段,提升了效率,詳細看下面的圖我相信你能夠理解的。
所以,記憶體池也可以提高記憶體利用率,解決外碎片的問題。
2.記憶體池的實現
對下面這種記憶體池進行實現,下面的記憶體池進行實現的時候,我們其實就是簡單的實現成為一個自由連結串列的形式,接下來,依靠圖示的情況為大家講解。
其實,與其說我們的記憶體池是一個記憶體池,並不如說是一個物件池,因為我們這裡的思路的主要核心就是通過物件的構造和析構來維護整個記憶體池。
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<cstdlib>
using namespace std;
template<typename T>
class objectpool
{
//首先給出記憶體池的節點,
struct BlockNode
{
void* _memory; //對應每個節點所掛的記憶體塊
BlockNode * _next; //指向下一個節點的指標。
size_t _objNum; //表示記憶體物件的個數
//進行記憶體塊的初始化
BlockNode(size_t objnum)
:_memory(NULL)
, _objNum(objnum)
{
_memory = malloc(_itemsize*objnum);
}
//記憶體塊析構
~BlockNode()
{
free(_memory);
_memory = NULL;
_next = NULL;
}
};
public:
//用來初始化物件大小的函式
static size_t IninItemSize();
//固定大小物件池大建構函式
objectpool(size_t initNum = 32, size_t maxNum = 100000)
:_countin(0)
, _Maximum(maxNum)
{
_first = _last = new BlockNode(initNum);
_lastDelete = NULL;
}
//物件池的析構
~objectpool()
{
Destory();
_first = _last = NULL;
////當把所有的記憶體塊進行釋放以後,這個時候_lastdelete不允許還鏈有記憶體塊
//
//if (_lastDelete==NULL)
// cout << "發生了記憶體洩漏" << endl;
}
void Destory()
{
//進行銷燬記憶體池的時候,我們這個時候採取的方式就是釋放每一個節點掛的memory。
BlockNode* cur = _first;
BlockNode* del = NULL;
while (cur)
{
del = cur;
cur = cur->_next;
delete del;
}
}
T* New()
{
//首先釋放以前的記憶體地址空間
//記憶體塊裡面進行申請
//申請新的節點物件
if (_lastDelete)
{
T* obj = _lastDelete;
//強轉為T**,然後解引用。這裡解決了32位程式和64為程式的限制,T**直接取到了正確的地址內容。
_lastDelete = *((T**)_lastDelete);
//此時從自由連結串列中拿出節點構造物件,所以不需要對countin++
//_countin++;
return new(obj)T;
}
//當BlockNode滿了的時候,這個時候就需要去到下一個BlockNode去構造物件。
if (_countin == _last->_objNum)
{
size_t newNodeNum = _last->_objNum * 2; /
if (newNodeNum >= _Maximum)
newNodeNum = _Maximum;
_last->_next = new BlockNode(newNodeNum);
if (_last->_next == NULL)
cout << "記憶體開闢失敗" << endl;
_countin = 0;
_last = _last->_next;
}
//在memory上剩餘的位置進行構造
T* obj = (T*)((char*)_last->_memory + _countin*_itemsize);
//T* obj = (T*)((T*)_last->_memory + _countin);
_countin++;
return new(obj)T;
}
void Delete(T* ptr)
{
//先呼叫解構函式,然後把要析構的物件的記憶體塊返回到記憶體池當中,也就是頭插入自由連結串列_lastdelete中。
ptr->~T();
if (ptr)
{
*(T**)ptr = _lastDelete;
_lastDelete = ptr;
}
}
private:
size_t _countin; //記錄當前節點使用的計數
BlockNode* _first;
BlockNode* _last;
size_t _Maximum; //記錄最大節點最大的物件數目
static size_t _itemsize ; //單個物件的大小
T* _lastDelete; //維護記憶體池
};
template<class T>
size_t objectpool<T>::IninItemSize()
{
//BlockNode中儲存了void* 的一個指標,所以最低限度你要開出來一個能存放void*的指標的大小的物件空間。
if (sizeof(T) <= sizeof(void*))
return sizeof(void*);
else
return sizeof(T);
}
template<class T>
size_t objectpool<T>::_itemsize = objectpool<T>::IninItemSize();