文章目錄
空間配置器(瞭解)
1.什麼是空間配置器
空間配置器,顧名思義就是爲各個容器高效的管理空間(空間的申請與回收)的,在默默地工作。雖然在常規使用STL時,可能用不到它,但站在學習研究的角度,學習它的實現原理對我們有很大的幫助。
2. 爲什麼需要空間配置器
前面在模擬實現vector、list、map、unordered_map等容器時,所有需要空間的地方都是通過new申請,delete釋放空間的。
new申請空間
1.operator new—>malloc
2.調用析構函數—完成對象的構造
雖然代碼可以正常運行,但是有以下不足之處:
- 有些構造函數調用不是必須的—構造函數意義不大還會影響程序性能
- vector和list中都可以存放元素—假設存放10000個元素—>都是一個一個的節點,需要使用12個字節,但實際申請了12+36個字節的空間,造成額外空間的浪費
- 頻繁向系統申請小塊內存塊malloc,容易造成內存碎片
- 頻繁向系統申請小塊內存,影響程序運行效率,性能會下降
- 空間申請與釋放需要用戶自己管理,容易造成內存泄漏
- 直接使用malloc與new進行申請,每塊空間前有額外空間浪費
- 申請空間失敗怎麼應對
- 代碼結構比較混亂,代碼複用率不高
- 未考慮線程安全問題
STL設計原則:通用性+高效率
因此需要設計一塊高效的內存管理機制
3. STL空間配置器的實現原理(版本:SGI-STL)
以上提到的幾點不足之處,最主要還是:頻繁向系統申請小塊內存造成的。那什麼纔算是小塊內存?SGI-STL以128作爲小塊內存與大塊內存的分界線,將空間配置器其分爲兩級結構,一級空間配置器處理大塊內存,二級空間配置器處理小塊內存。
3.1STL空間配置器如何實現的?
頻繁向系統申請一些小的內存塊
大於128字節—>大內存塊,一級空間配置器
小於等於128字節—>小內存塊,二級空間配置器
3.2一級空間配置器
實現原理: malloc+free—>set_new_handle
以下代碼來自於stl_alloc.h
template <int inst>
class _malloc_alloc_template
{
private:
static void *oom_malloc(size_t);
public:
// 對malloc的封裝
static void * allocate(size_t n)
{
// 申請空間成功,直接返回,失敗交由oom_malloc處理
void *result = malloc(n);
if (0 == result)
result = oom_malloc(n);
return result;
}
// 對free的封裝
static void deallocate(void *p, size_t /* n */)
{
free(p);
}
// 模擬set_new_handle
// 該函數的參數爲函數指針,返回值類型也爲函數指針
// void (* set_malloc_handler( void (*f)() ) )()
static void(*set_malloc_handler(void(*f)()))()
{
void(*old)() = _malloc_alloc_oom_handler;
_malloc_alloc_oom_handler = f;
return(old);
}
};
// malloc申請空間失敗時代用該函數
template <int inst>
void * _malloc_alloc_template<inst>::oom_malloc(size_t n)//系統內存空間不足進入該函數
{
void(*my_malloc_handler)();
void *result;
for (;;)
{
// 檢測用戶是否設置空間不足應對措施,如果沒有設置,拋異常,模式new的方式
my_malloc_handler = _malloc_alloc_oom_handler;//接收該函數指針,用戶提供的函數:空間不足的應對措施
if (0 == my_malloc_handler)//驗證該函數指針是否爲空,是:直接拋異常
{
_THROW_BAD_ALLOC;
}
// 如果設置,執行用戶提供的空間不足應對措施
(*my_malloc_handler)();//調用函數指針對應的函數
// 繼續申請空間,可能就會申請成功
result = malloc(n);
if (result)
return(result);
//申請失敗,循環繼續
}
}
typedef _malloc_alloc_template<0> malloc_alloc;
3.3二級空間配置器
爲了解決頻繁向系統申請小的內存塊造成的缺陷
二級空間配置器專門負責處理小於128字節的小塊內存。如何才能提升小塊內存的申請與釋放的方式呢?SGISTL採用了內存池的技術來提高申請空間的速度以及減少額外空間的浪費,採用哈希桶的方式來提高用戶獲取空間的速度與高效管理。
1.內存池技術
內存池就是:先申請一塊比較大的內存塊已做備用,當需要內存時,直接到內存池中去去,當池中空間不夠時,再向內存中去取,當用戶不用時,直接還回內存池即可。避免了頻繁向系統申請小塊內存所造成的效率低、內存碎片以及額外浪費的問題。
當用戶需要4個字節時,將_start向後移動4個字節
當用戶需要8、10個字節時,將_start向後移動18個字節
空間申請:res=_start;_start=+n;
用戶用完時會歸還,但直接歸還並不簡單,所以用鏈表結構—>管理用戶歸還的內存塊
當用戶需要再次申請時:最好優先從歸還鏈表中找到需要的大小內存塊,除非找不到纔到內存池剩餘塊中找,如上圖,假如經過多次從內存池剩餘塊中申請內存,內存池剩餘塊最後只有10個字節,當這時用戶需要16個字節內存塊時,內存池已經給不出來了,因此,最好不要一直申請內存池剩餘塊中的內存,只要下面歸還的內存塊鏈表中能夠找到就儘量使用歸還的內存塊!
大致步驟就是:
-
現在已經歸還的內存塊中找合適塊
-
找到—>是否需要分割
不需要—>直接分配
需要—>分割成需要的大小的內存塊 -
未找到—>到內存池中申請
缺陷
效率低—>O(N)—>哈希—>O(1)
分割多次就會導致內存塊越來越小,只能解決外部碎片(空間固定爲x的倍數:根據系統選擇)最少爲4的倍數—>32位系統:
最少爲8的倍數—>64位系統(會出現浪費過多):128/8
申請歸還比較高效
簡單描述,僅供參考
void allocate(size_t n)
{
if (n > 128)
;//調用一級空間適配器申請
//1.用n計算桶號
//2.檢測該桶中是否有節點(內存塊)
//有:使用頭刪法將第一個內存塊返回
//沒有:refill(Rount_up(n));
}
void deallocate(void* p, size_t n)
{
if (n > 128)
;
//1.計算桶號
//2.將該塊內存掛接到對應的痛
}
void* refill(size_t n/*已經是8的整數倍*/)
{
size_t nobjs = 20;
char* chunk = chunk_alloc(n, nobjs);
if (nobjs == 1)
return chunk;
//1<n<=20
//將第一個內存塊保存,最後要返回值給外部用戶
//將剩餘nobjs-1個內存塊掛接到對應的桶中
}
void* chunk_alloc(size_t size, size_t& nobjs/*20*/)
{
size_t totalBytes = nobjs * size;
size_t leftBytes = _finish - _start;
void* res = nullptr;
if (leftBytes >= totalBytes)//內存池空間衝突,可以提供20塊
{
res = _start;
_start += totalBytes;
return res;
}
else if (leftBytes >= size)//內存池不能提供20塊,至少可以提供一塊
{
nobjs = leftBytes / size;
res = _start;
_start += nobjs * size;
return res;
}
else//leftBytes<size 內存池空間不足,連一塊都提供不了
{
//1.將內存池中剩餘的內存塊(<128)--->掛接到對應的桶中
//2.total_get=2*total_bytes+RoundUP(heap_size>>4);
_start = malloc(total_get);
if (_start==nullptr)
{
//1.在哈希桶中找更大的內存塊:
//找到了:將該內存塊放到內存池中
return chunk_alloc(nobjs,n);
//未找到:
//2.求助一級空間配置器---空間不足的應對措施
}
heap_size += total_get;
finish = _start + total_get;
return chunk_alloc(nobjs,n);
}
}