煙臺h5網站建設公司游戲代理免費加盟
目錄
- 定長內存池設計
- 設計思路
- 具體實現(xiàn)
- 定長內存池初始化
- T*New()申請內存
- 代碼
- void Delete(T* obj)回收內存
- 代碼
- 設計的總代碼
- 測試代碼
- Objectpool.h文件代碼
- test.cpp文件代碼
- 拓展windows和Linux下如何直接向堆申請頁為單位的大塊內存:
感謝各位大佬對我的支持,如果我的文章對你有用,歡迎點擊以下鏈接
🐒🐒🐒 個人主頁
🥸🥸🥸 C語言
🐿?🐿?🐿? C語言例題
🐣🐣🐣 python
🐓🐓🐓 數據結構C語言
🐔🐔🐔 C++
🐿?🐿?🐿? 文章鏈接目錄
🤠🤠🤠 高并發(fā)內存池項目
定長內存池設計
設計思路
申請一大塊內存,用一個指針指向申請的內存,這里的難點是內存釋放,因為我們申請的是一塊很大的內存,而使用的時候都是將這一大塊內存切成一小塊一小塊的用,而用完后釋放我們不可以直接將他還給系統(tǒng),因為釋放的內存太小了,如果還給系統(tǒng)就會出現(xiàn)內存碎片的問題,所以我們是要將需要釋放的內存管理起來,當他們都釋放后再歸還內存,這里的管理方式就是用一個鏈表將內存管理起來
由于是定長內存池,這里的定長就是切固定大小的內存
具體實現(xiàn)
定長內存池初始化
首先我們需要一個類
class Objectpool
{};
由于是定長內存池,所以類里面一點要有一個指針指向申請的內存空間,為了方便切割內存我們使用char*_memory,因為char是可以精確到1個字節(jié)1個字節(jié)的切割比較方便,如果是int的話那么我們要切割的長度就是4的倍數
此外因為申請內存是固定的我們可以用非類型的模版參數template<size_t N>表示我們申請的內存是一個固定大小為N
當然我們也可以用template表示ObjectPool每次獲取的對象都是一個T對象,T的大小是固定的,這里我們選擇的是用template
template<class T>
class ObjectPool
{char* _memory=nullptr;
};
我們還需要考慮內存回收
當我們需要切割內存時_memory需要往后移動
當切割后的內存用完后需要回收我們需要用一個鏈表將他們串起來
可是現(xiàn)在有一個問題我們應該怎么樣才能將他們串起來,我們是否需要再定義一個結構體將他們鏈接起來呢?
其實是不需要的,我們可以直接在內存當中修改,將后面的內存塊地址存到前一個內存塊當中,但是這里有一個要求就是32位下的指針大小是4個字節(jié),而64位下的指針大小是8個字節(jié),也就意味著當我們要存一個內存塊的空間時,32位下內存塊至少要有4個字節(jié),而64位則至少要有8個字節(jié)
在32位下對于一個內存塊有100字節(jié)我們則需要將他的頭4個字節(jié)進行修改保存下一塊內存的地址
T*New()申請內存
申請內存我們需要先看_memory是否為空,如果_memory為空的話那么就需要申請內存
T* New()
{if (_memory==nullptr)//剩余內存不夠一個對象大小是重新開大塊空間{_memory =(char*) malloc(128 * 1024);//128kbif (_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繼續(xù)+= sizeof(T)就會造成越界,因為申請的內存以及用完了,而_memory現(xiàn)在在訪問的是別的空間
為了解決這個問題我們還需要有一個remainBytes去記錄還剩多少空間,當remainBytes<sizeof(T)的時候就意味著剩下的空間以及不足以支持我們繼續(xù)切割了,可能需要重新申請一塊空間
為什么是可能需要重新申請一塊空間呢,因為_freeList是回收釋放的內存,如果_freeList中有回收了的內存,那么這些內存是可以繼續(xù)重復利用的
所以一開始需要一個指針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個字節(jié)的,int類型的話也就剛好4個字節(jié),但是在64位下是不夠的,所以需要判斷sizeof(T) < sizeof(void*) ,表示T的內存大小是否小于一個指針,如果小于的話就要更改切割內存的大小
最后我們還需要對我們創(chuàng)造的對象進行初始化
這里用定位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);//128kbif (_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個字節(jié)存空地址,具體辦法就是讓傳入的指針強轉成int*,那么我們對int進行解引用將前4個字節(jié)更改為nullptr就可以實現(xiàn)了
但是這里有一個問題就是int只有4個字節(jié),這種方法只適用于32位下,在64位下是不行的
當然我們可以用if語句判斷一個指針的大小,然后再進行細分也是可以的
下面有更簡單的方式就是將obj強轉成void**,也就是二級指針(其他類型的二級指針也是可以的,主要用的就是二級指針轉換成一級指針的空間大小是4/8個字節(jié)),然后我們對他解引用,*(void**)obj = nullptr
上面是解決第一次內存塊回收的問題,下面是解決多次回收的問題
多個內存塊回收我們選擇用頭插的方式串起來,因為尾插效率太低
首先還是要讓新的內存塊保存_freeList指向的地址,然后讓_freeList指向新的內存塊
void Delete(T* obj)
{if (_freeList==nullptr){_freeList = obj;*(void**)obj = nullptr;}else{*(void**)obj = _freeList;_freeList = obj;}
}
我們發(fā)現(xiàn)上面代碼中
*(void**)obj = _freeList;_freeList = obj;
這段代碼是適用于所有情況的
一開始_freeList=nullptr
讓obj前4/8個字節(jié)保存_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);//128kbif (_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;//大塊內存切割過程中的剩余字節(jié)數
};
測試代碼
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);//128kbif (_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;//大塊內存切割過程中的剩余字節(jié)數
};
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;
}
拓展windows和Linux下如何直接向堆申請頁為單位的大塊內存:
VirtualAlloc
Linux進程分配內存的兩種方式–brk() 和mmap()