深度解析--SGI STL 空間配置與釋放
阿新 • • 發佈:2018-11-19
歡迎大家來訪二笙的小房子,一同學習分享生活!
文章目錄
- 1.SGI 空間配置與釋放
- 2. 兩級配置器
- 3. C++ new-handler機制
- 4. free list的節點結構
1.SGI 空間配置與釋放
- 物件構造前的空間配置與物件析構後的空間釋放,由<stl_alloc.h>負責,SGI主要考慮以下四個步驟:
1.向system heap要求空間
2.考慮多執行緒狀態
3.考慮記憶體不足時的應對措施
4.考慮過多“小型區塊”可能造成的記憶體碎片問題 - SGI以malloc()和free()完成記憶體的配置與釋放,但考慮到小型區塊可能造成的記憶體破碎問題,SGI設定了雙層級配置器:
1.第一級配置器直接使用malloc()和free()
2.第二級配置器視情況採用不同的策略:
- 配置區塊超過128bytes時,視之為足夠大,便採用第一級配置器
- 配置區塊小於128bytes時,視之為過小,採用
memory pool
整理方式
2. 兩級配置器
- SGI封裝了一個介面,名為simple_alloc(
使配置器的介面符合STL規格
),無論alloc被定義為第一級亦或第二級配置器,都採用該介面進行空間配置:
template <class T, class Alloc>
class simple_alloc {
public:
static T *allocate(size_ t n) //單純的轉呼叫函式,呼叫傳遞給配置器的成員函式
{return 0 == n? 0 : (T*) Alloc::allocate(n * szieof(T)); }
static T *allocate(void)
{ return (T*) Alloc::allocate(sizeof (T)) ; }
static void deallocate(T *p, size_t n)
{ if (0 != n) Alloc::deallocate(p, n * sizeof(T)); }
sattic void deallocate(T *p)
{ Alloc::deallocate(p, sizeof(T)); }
};
2.1 一、二級配置器的關係
- 在學習兩級配置器之前首先通過下圖瞭解一下一、二級配置器是如何實現的以及兩者之間的關係:
2.2 第一級配置器 __malloc_alloc_template
- 主要以malloc()、free()、realloc()等C函式執行實際的記憶體配置、釋放、重配置操作,並實現出C++ new-handler機制:
#include <iostream>
#define __throwBadAlloc cerr << "out of memory" << endl; exit(1);
template <int inst>
class __MallocAllocTemplate {
typedef void(*OOM_HANDLE)();
private:
//以下函式用來處理記憶體不足的情況,oom:out of memory
static void *oomMalloc(size_t);
static void *oomRealloc(void *, size_t);
static OOM_HANDLE OOM_Handle;
public:
static void * allocate(size_t n) {
void *result = malloc(n); //第一級配置器直接使用malloc
//如果記憶體不足則呼叫oomMalloc處理
if (0 == result) result = oomMalloc(n);
return result;
}
static void deallocate(void *p, size_t m) {
free(n); //第一級配置器直接呼叫free
}
static void * reallocate(void *p, size_t oldSz, size_t newSz) {
void *result = realloc(p, newSz); //第一級配置器直接呼叫realloc()
//記憶體不足呼叫oomRealloc
if (0 == result) result = oomRealloc(p, newSz);
return result;
}
//static void (* set_malloc_handle (void (*f)())) ()
//引數與返回值都是void (*)()
static OOM_HANDLE setMallocHandele(OOM_HANDLE f) {
OOM_HANDLE old = OOM_Handle;
OOM_Handle = f;
return old;
}
};
//設定初始指標為空
template <int inst>
void(*__MallocAllocTemplate<inst>::OOM_Handle)() = NULL;
//記憶體不足嘗試再次申請
template <int inst >
void * __MallocAllocTemplate<inst>::oomMalloc(size_t n) {
void(*myMallocHandle)();
void *result;
for (;;) {
myMallocHandle = OOM_Handle; //不斷嘗試釋放、配置、再釋放、再配置...
if (0 == myMallocHandle) { __throwBadAlloc; }
(*myMallocHandle)(); //呼叫處理例程,企圖釋放記憶體
result = malloc(n); //再次嘗試配置記憶體
if (result) return(result);
}
}
template <int inst >
void * __MallocAllocTemplate<inst>::oomRealloc(void *p, size_t n) {
void(*myMallocHandle)();
void *result;
for (;;) {
myMallocHandle = OOM_Handle; //不斷嘗試釋放、配置、再釋放、再配置...
if (0 == myMallocHandle) { __throwBadAlloc; }
(*myMallocHandle)(); //呼叫處理例程,企圖釋放記憶體
result = malloc(n); //再次嘗試配置記憶體
if (result) return(result);
}
}
- 若記憶體不足處理函式oomMalloc與oomRealloc未設定
記憶體不足處理例程
,則直接丟出bad_alloc異常資訊
2.3 第二級配置器
- 第二級配置器多了一些機制,避免小額區塊造成記憶體的碎片,減少配置時的額外負擔
- 具體實現:
1.如果區塊夠大,超過128bytes,移交第一級配置器處理
2.當區塊小於128bytes,則以記憶體池管理:
- 每次配置一塊記憶體,維護對應的自由連結串列;
- 下次收到相同大小的請求時,則直接從自由連結串列中拔出;
- 客端釋還小額區塊,就回收到free-lists中
- 第二級配置器主動將任何小額區塊的記憶體需求上調至8的倍數(ROUND_UP函式實現)
- 以下是部分函式實現:
//提升函式,將bytes提升至8的倍數
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}
//16個free-lists
static obj * volatile free_list[__NFREELISTS];
//填充free_list
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes) + __ALIGN - 1)/__ALIGN - 1);
}
以下重點理解
2.3.1 空間配置函式alllocate()
- 步驟:
1.判斷區塊大小,大於128bytes呼叫第一級配置器
2.小於128bytes檢查對應的free_list,如果有可用區塊,直接拿來用;否則上調至8倍數邊界,呼叫refill為free_list重新填充
static void * allocate(size_t n) {
obj * volatile * my_free_list;
obj * result;
if (n > (size_t) __MAX_BYTES) { //大於128呼叫第一級
return(malloc_alloc::allocate(n));
}
my_free_list = free_list + FREELIST_INDEX(n); //定位下標
result = *my_free_list;
if (result == 0) {
void *r = refill(ROUND_UP(n)); //沒找到可用的free_list,重新填充free_list
return r;
}
//調整free_list
*my_free_list = result->free_list_link;
return (result);
};
2.3.2 空間釋放函式 deallocate()
- 步驟:
1.判斷區塊大小,大於128bytes呼叫第一級配置器
2.小於128bytes則找出對應的free_list,將區塊回收
static void deallocate(void *p, size_t n) {
obj *q = (obj *)p;
obj * volatile * my_free_list;
if (n > (size_t) __MAX_BYTES) { //大於128呼叫第一級配置器
malloc_alloc::deallocate(p, n);
return ;
}
my_free_list = free_list + FREELIST_INDEX(n);
q-> free_list_link = *my_free_list; //調整free_list,回收區塊
*my_free_list = q;
}
2.3.3 重新裝填free_list
- 步驟:
1.free list中無可用區塊,呼叫refill(),為free list填充空間
2.新空間取自記憶體池(由chunk_alloc())完成,預設取20個新節點
3.如果只申請到一塊,則分配給呼叫者;否則在chunk空間建立free list
template <bool threads, int inst>
void * __default_alloc_template<threads, inst>::refill(size_t n) {
int nobjs = 20; //預設取20塊新節點
char * chunk = chunk_alloc(n, nobjs); //在記憶體池中申請
if (1 == nobjs) return(chunk); //只獲得一塊區塊,給呼叫者使用
//否則跳整free list/,納入新節點
obj * my_free_list = free_list + FREELIST_INDDEX(n);
obj * result = (obj*)chunk; //返回一塊給客端
obj * next = (obj*)(chunk + n); //指向下一塊記憶體
obj * cur = next;
*my_free_list = cur;
//在chunk空間建立free list連線
for (int i = 1; i < nobjs; ++i) {
next = (obj*)((char*)next + n);
cur->free_list_link = next;
cur = next;
}
cur->free_list_link = NULL;
return result;
}
2.3.4 記憶體池取空間(chunk_alloc實現)
- 步驟(主要分以下四個步驟進行):
1. 記憶體池剩餘空間完全滿足需求,則直接分配給使用者
2. 若剩餘空間不能完全滿足,但足夠提供一個以上的區塊,則將這些區塊分配給使用者
3. 若剩餘空間一個都滿足不了,則將殘餘空間編入free list,然後配置heap空間:
1.向系統heap申請空間
2. 分配成功則返回
3. 分配失敗則檢索自己的free list檢視是否有可用區塊
4. 到處都無記憶體使用則呼叫第一級配置器
template <bool threads, int inst>
char *
__default_alloc_template<threads, inst>::
chunk_alloc(size_t size, int& nobjs) {
char *result;
size_t totalbytes = size * nobjs; //需求量
size_t bytesleft = end_free - start_free; //記憶體池剩餘量
//如果剩餘大於需求
if (bytesleft >= totalbytes) {
result = start_free;
start_free += totalbytes;
resturn result;
}
//剩餘只能滿足一個或以上的區塊,不能全部滿足
else if(bytesleft >= size) {
nobjs = bytesleft/size;
totalbytes = size * nobjs;
result = start_free;
start_free += totalbytes;
return result;
}
//剩餘空間一個區塊都不能滿足
else {
//記憶體池中還有些許零頭 ,將其分配給使用者
if (bytesleft >0) {
//尋找適當的free list
obj * volatile *myfreelist = free_list + FREELIST_INDEX(bytesleft);
((obj *)start_free)->free_list_link = *myfreelist;
*myfreelist = (obj *)start_free;
}
size_t bytesToGet = 2 * totalbytes + ROUND_UP(heap_size>>4); //配置heap空間,初始量設為需求的兩倍加上一個隨配置次數增大的附加量
start_free = (char *)malloc(bytesToGet);
if (0 == start_free) {
//heap空間不足,malloc失敗
int i;
obj * volatile * myfreelist, *p;
//搜尋free list,查詢是否還有未用的區塊
for (i = size; i <= _MAX_BYTES; i += _ALIGN) {
myfreelist = free_list + FREELIST_INDEX(i);
p = *myfreelist;
if (0 != p) {
//調整free list釋放未用區塊
myfreelist = p -> free_list_link;
start_free = (char*)p;
end_free = start_free + i;
return (chunk_alloc(size, nobjs)); //遞迴呼叫,修正nobjs
}
}
//所有地方都無記憶體可用,則呼叫第一級配置器,使用out of memory機制
end_free = 0;
start_free = (char *)malloc_alloc::allocate(bytesToGet);
}
//分配成功則返回
heap_size = bytesToGet;
end_free = start_free + bytesToGet;
return (chunk_alloc(size, nobjs)); //遞迴修正nobjs
}
}
3. C++ new-handler機制
本章在講解第一級配置器時初次提及C++ new-handler機制,於是就去《Effective C++》進一步瞭解了有關new-handler機制,日後再次遇到時也能迅速回憶起。
- 1.“當operator new丟擲異常以反映一個未獲滿足的記憶體需求之前,它會先呼叫一個客戶指定的錯誤處理函式,一個所謂的new-handler”,這是Effective C++中的原話,用以引出new-handler,從這句話我們大概能知道new-handler是用來處理記憶體不足的一個機制
- 2.為了指定這個“用以處理記憶體不足”的函式,客戶需要呼叫
set_new_handler
,定義於< new>的一個標準程式庫函式:
namespace std {
typedef void (*new_handler)();
//返回值和引數都是new_handler
new_handler set_new_handler(new_handler p) throw();
}
- 3.set_new_handler的引數是一個指標,指向operator new無法分配足夠記憶體時需要呼叫的函式:
void outOfMem()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
std::set_new_handler(outOfMem); //當記憶體不足時,呼叫處理函式outOfMem
int* pBigDataArrry = new int[100000000L];
...
}
- 4.設計一個良好的new-handler:
4.1 讓更多記憶體可被使用:程式一開始執行就分配一大塊記憶體,而後當new-handler第一次被呼叫,將它們釋放還給程式
4.2 安裝另一個new-handler:令new-handler修改一些會影響new-handler行為的資料
4.3 解除安裝new-handler將NULL指標傳給set_new_handler
4.4 丟擲bad_alloc的異常
4.5 不返回:呼叫abort或exit
- 5.基於以上設計,你可以定製你自己的new-handle機制
4. free list的節點結構
- 通過上圖可以瞭解到STL通過指標來維護自由連結串列,有人可能會想每個節點都需要額外的指標,會不會造成額外的負擔呢?答案是會的,那麼STL的設計精妙之處在哪呢?我們可以先來看一看它的節點結構:
union obj {
union obj * free_list_link;
char client_data[1];
}
- "上述obj所用的union,由於union之故,從其第一欄位觀之,obj可被視為一個指標,指向相同形式的另一個obj,從其第二欄位觀之,obj可被視為一個指標,指向實際區塊“,這段話前半部分很好理解,因為free_list_link本身就是一個obj的指標,但後半部分
obj可被視為一個指標,指向實際區塊
,這一句話又怎麼理解呢?
1.首先我們回顧下union的性質,一個union類任意時刻只允許其一個數據成員有值,且資料成員公用記憶體
2.由上,該free list節點結構union中的資料成員free_list_link和client_data公用一塊記憶體,前者是指向下一節點的指標,後者是指向實際記憶體的指標,無論什麼情況,二者只用其一,不分配的時候,節點就放在空閒連結串列之內,節點內容是指向下一節點的地址,如果被分配了,那麼節點指向的就是實際的記憶體