SGI STL學習筆記(1):空間配置器(allocator)
SGI STL設計了雙層級配置器來完成空間的配置與釋放。當配置區塊超過128bytes時,呼叫第一級配置器;當配置區塊小於128bytes時,呼叫第二級配置器。下面通過原始碼來詳細說明。
第一級配置器 __malloc_alloc_template
第一級配置器是對malloc、realloc以及free的封裝,當呼叫malloc和realloc申請不到記憶體空間的時候,會改呼叫oom_malloc()和oom_realloc(),這兩個函式會反覆呼叫使用者傳遞過來的out of memory handler處理函式,直到能用malloc或者realloc申請到記憶體為止。如果使用者沒有傳遞__malloc_alloc_oom_handler,__malloc_alloc_template會丟擲__THROW_BAD_ALLOC異常。
#if 0
# include <new>
# define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
# include <iostream.h>
# define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)
#endif
template <int inst>
class __malloc_alloc_template {
private:
static void* oom_malloc(size_t);
static void* oom_realloc(void*, size_t);
static void (* __malloc_alloc_oom_handler)();
public:
static void* allocate(size_t n)
{
// 第一級配置器直接使用malloc()
void* result = malloc(n);
// 無法滿足需求時,改用oom_malloc()
if(0 == result) result = oom_malloc(n);
return result;
}
static void deallocate(void* p, size_t /* n */)
{
// 第一級配置器直接使用free()
free(p);
}
static void* reallocate(void* p, size_t /* old_sz */, size_t new_sz)
{
// 第一級配置器直接使用realloc()
void* result = realloc(p, new_sz);
// 無法滿足需求時,改用oom_realloc()
if(0 == result) result = oom_realloc(p, new_sz);
return result;
}
// 指定自己的out-of-memory handler
static void (* set_malloc_handler(void (*f)()))()
{
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
// 初值為0。有待客端設定
template <int inst>
void (* __malloc_alloc_template::__malloc_alloc_oom_handler)() = 0;
// oom_malloc和oom_realloc都有內迴圈,不斷呼叫“記憶體不足處理例程”
// 期望在某次呼叫之後,獲得足夠的記憶體而圓滿完成任務
// 如果尚未設定“記憶體不足處理例程”,則會丟出異常,或利用exit(1)硬生生中止程式
template <int inst>
void* __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
void (* my_malloc_handler)();
void* result;
for(;;) {
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);
}
}
template <int inst>
void* __malloc_alloc_template<inst>::oom_realloc(void* p, size_t n)
{
void (* my_malloc_handler)();
void* result;
for(;;) {
my_malloc_handler = __malloc_alloc_oom_handler;
if(0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
(*my_malloc_handler)();
result = realloc(p, n);
if(result) return(result);
}
}
// 直接將inst指定為0
typedef __malloc_alloc_template<0> malloc_alloc;
第二級配置器 __default_alloc_template
enum { __ALIGN = 8; } // 小型區塊的上調邊界
enum { __MAX_BYTES = 128; } // 小型區塊的上限
enum { __NFREELISTS = __MAX_BYTES/__ALIGN; } // free list個數,16
// 第一引數用於多執行緒環境,暫不討論
template <bool threads, int inst>
class __default_alloc_template {
private:
// ROUND_UP()將bytes上調至8的倍數
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));
}
private:
// free list的節點構造
union obj {
union obj* free_list_link; // 指向相同形式的另一個obj
char client_data[1]; // 指向實際的區塊
};
private:
// 16個free-lists
static obj* volatile free_list[__NFREELISTS];
// 根據區塊大小決定使用第n號free list,n從0起算
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes) + __ALIGN - 1) / __ALIGN - 1);
}
// 為free lists重新填充空間
static void* refill(size_t n);
// 配置一大塊空間,可容納nobjs個大小為size的區塊,nobjs根據實際情況可能會降低
static char* chunk_alloc(size_t size, int &nobjs);
static char* start_free; // 記憶體池起始位置
static char* end_free; // 記憶體池結束位置
static size_t heap_size; // 記憶體池大小
public:
static void* allocate(size_t n);
static void deallocate(void *p, size_t n);
static void* reallocate(void *p, size_t old_sz, size_t new_sz);
};
// 以下是靜態資料成員的定義和初值設定
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::start_free = 0;
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::end_free = 0;
template<bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;
template<bool threads, int inst>
__default_alloc_template<threads, inst>::obj* volatile
__default_alloc_template<threads, inst>::free_list][__NFREELISTS] =
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
第二級配置器每次配置一大塊記憶體,都需要維護對應的free list,free lists的構造如下圖:
第二級配置器的記憶體分配過程如下:
1.如果申請的記憶體空間大於128bytes, 則交由第一個分配器處理
2.分配器首先將申請記憶體的大小上調至8的倍數n(ROUND_UP函式),並根據n找出其對應的空閒連結串列地址my_free_list
3.如果該空閒連結串列中有可用的空閒塊,則將此空閒塊返回並更新my_free_list,否則轉到4
4.到這一步,說明my_free_list中沒有空閒塊可用了,分配器會按照下面的步驟處理:
a) 試著呼叫chunk_alloc()申請大小為n*20的記憶體空間,注意的是,此時不一定能申請到n*20大小的記憶體空間
b) 如果只申請到大小為n的記憶體空間,則返回給使用者,否則到c)
c) 將申請到的n*x(a中說了,不一定是n*20)記憶體塊取出一個返回給使用者,其餘的記憶體塊鏈到空閒連結串列my_free_list中
// 空間配置函式allocate()
static void* allocate(size_t n)
{
obj* volatile* my_free_list;
obj* result;
// 判斷區塊大小,如果大於128就呼叫第一級配置器
if(n > (size_t)__MAX_BYTES){
return(malloc_alloc::allocate(n));
}
// 小於128就檢查對應的free lists,如果有可用區塊就直接拿來用
// 如果沒有可用區塊,就將區塊大小上調至8倍數邊界,然後呼叫refill()為free lists重新填充空間
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
if(result == 0) {
void* r = refill(ROUND_UP(n));
return r;
}
*my_free_list = result->free_list_link;
return (result);
}
記憶體的釋放過程比較簡單,它接受兩個引數,一個是指向要釋放的記憶體塊的指標p,另外一個表示要釋放的記憶體塊的大小n。分配器首先判斷n,如果n>128bytes,則交由第一級配置器去處理,否則將該記憶體塊加到相應的空閒連結串列中。
// 空間釋放函式deallocate()
static void deallocate(void* p, size_t n)
{
obj* q = (obj*)p;
obj* volatile* my_free_list;
// 判斷區塊大小,如果大於128就呼叫第一級配置器
if(n > (size_t)__MAX_BYTES){
malloc_alloc::deallocate(p, n);
return;
}
// 小於128就找出對應的free lists,將區塊回收
my_free_list = free_list + FREELIST_INDEX(n);
q->free_list_link = *my_free_list;
*my_free_list = q;
}
// 重新填充的新的空間取自記憶體池
// 預設取得20個新區塊,但萬一記憶體池空間不足,獲得的區塊數可能小於20
template<bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
int nobjs = 20;
// 呼叫chunk_alloc(),嘗試取得nobjs個區塊作為free list的新節點
// 這裡的引數nobjs是按引用傳遞的(pass by reference)
char* chunk = chunk_alloc(n, nobjs);
obj* volatile* my_free_list;
obj* result;
obj* current_obj, * next_obj;
int i;
// 如果只獲得一個區塊,這個區塊就分配給呼叫者用,free list無新節點
if(1 == nobjs) return(chunk);
//否則準備調整free list,納入新節點
my_free_list = free_list + FREELIST_INDEX(n);
// 這一塊準備返回給客端
result = (obj*)chunk;
// 以下導引free list指向新配置的空間
*my_free_list = next_obj = (obj*)(chunk + n);
// 以下將free list的各節點串接起來
// 從1開始,因為第0個將返回給客端
for(i = 1; ; i++) {
current_obj = next_obj;
next_obj = (obj*)((char*)next_obj + n);
// 到達最後一個節點
if(nobjs - 1 == i) {
current_obj->free_list_link = 0;
break;
} else {
current_obj->free_list_link = next_obj;
}
}
return(result);
}
chunk_alloc()的具體過程如下:
1.如果start_free和end_free之間的空間足夠分配size*20大小的記憶體空間,則從這個空間中取出size*20大小的記憶體空間,更新start_free並返回申請到的記憶體空間的起始地址,否則轉到2
2.如果start_free和end_free之間的空間足夠分配大於size的記憶體空間,則分配整數倍於size的記憶體空間,更新start_free,由nobj返回這個整數,並返回申請到的記憶體空間的起始地址,否則轉到3
3.到這一步,說明記憶體池中連一塊大小為size的記憶體都沒有了,此時如果記憶體池中還有一些記憶體(這些記憶體大小肯定小於size),則將這些記憶體插入到其對應大小的空閒分割槽鏈中
4.呼叫malloc申請大小為(2*n*20 + 附加量)的記憶體空間, 如果申請成功,更新start_free, end_free和heap_size,並重新呼叫chunk_alloc(),否則轉到5
5.到這一步,說明4中呼叫malloc失敗,這時配置器依次遍歷16個空閒分割槽鏈,只要有一個空閒鏈,就釋放該鏈中的一個節點,重新呼叫chunk_alloc()
// chunk_alloc()從記憶體池取空間給free list使用
// 假設size已經適當上調至8的倍數
// 引數nobjs是按引用傳遞
template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::
chunk_alloc(size_t size, int& nobjs)
{
char* result;
size_t total_bytes = size * nobjs;
size_t bytes_left = end_free - start_free;
// 記憶體池剩餘空間完全滿足需求量,就直接調出20個區塊返回給free list
if(bytes_left >= total_bytes) {
result = start_free;
start_free += total_bytes;
return(result);
} else if(bytes_left >= size) {
// 記憶體池剩餘空間不能完全滿足需求量,但足夠供應一個(含)以上的區塊,就撥出這些空間出去
// 此時按引用傳遞的nobjs被修改為實際能夠供應的區塊數
nobjs = bytes_left / size;
total_bytes = size * nobjs;
result = start_free;
start_free += total_bytes;
return(result);
} else {
// 記憶體池剩餘空間連一個區塊的大小也無法提供,就需要利用malloc從heap中配置記憶體
// 新水量的大小為需求量的兩倍,再加上一個隨著配置次數增加而愈來愈大的附加量
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
// 讓記憶體池殘餘的零頭還有利用價值
if(bytes_left > 0) {
obj* volatile* my_free_list = free_list + FREELIST_INDEX(bytes_left);
((obj*)start_free)->free_list_link = *my_free_list;
*my_free_list = (obj*)start_free;
}
start_free = (char*)malloc(bytes_to_get);
// heap空間不足,malloc失敗
if(0 == start_free) {
int i;
obj* volatile* my_free_list, *p;
// 遍歷空閒分割槽鏈,如果尚有未用區塊,就釋放該區塊,然後遞迴呼叫chunk_alloc()
for(i = size; i <= __MAX_BYTES; i += __ALIGN) {
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if(0 != p) {
*my_free_list = p->free_list_link;
start_free = (char*)p;
end_free = start_free + i;
return(chunk_alloc(size, nobjs));
}
}
// 如果到處都沒記憶體可用了,就呼叫第一級配置器,看看oom機制能否有效
// 這會導致丟擲異常,或記憶體不足的情況得到改善
end_free = 0;
start_free = (char*)malloc_alloc::allocate(bytes_to_get);
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
// 遞迴呼叫自己
return(chunk_alloc(size, nobjs));
}
}
參考資料:
1.《STL原始碼剖析》
2.STL原始碼學習—-記憶體管理. http://www.cnblogs.com/cobbliu/archive/2012/04/05/2431804.html
相關文章
- SGI STL學習筆記(2):traits程式設計技法筆記AI程式設計
- SGI STL學習筆記(3):copy演算法實現細節筆記演算法
- SGI STL 的記憶體管理記憶體
- go 學習筆記之工作空間Go筆記
- OpenCV 名稱空間學習筆記OpenCV筆記
- 【 PHP 學習筆記 】名稱空間PHP筆記
- [ PHP 學習筆記 ] 名稱空間PHP筆記
- DB2學習筆記 - 表空間DB2筆記
- STL-空間配置器、迭代器、traits程式設計技巧AI程式設計
- STL的學習筆記之一 (轉)筆記
- 機器學習-學習筆記(一) --> (假設空間 & 版本空間)及 歸納偏好機器學習筆記
- TP5學習筆記一 名稱空間筆記
- C++ 學習筆記之——STL 庫 queueC++筆記
- C++ 學習筆記之 STL 佇列C++筆記佇列
- nginx學習筆記(1):配置項的解析Nginx筆記
- Maven 學習筆記——Maven環境配置(1)Maven筆記
- 學習筆記1筆記
- 學習筆記-1筆記
- C++學習筆記 — STL標準模板庫C++筆記
- Spring Boot 學習筆記(4):配置properties(1)Spring Boot筆記
- 【OCP學習筆記】配置網路環境 -- 1筆記
- [ITIL學習筆記]之配置管理(1)薦筆記
- angular學習筆記(十六) -- 過濾器(1)Angular筆記過濾器
- oracle臨時表空間學習筆記 增刪改查Oracle筆記
- swift學習筆記《1》Swift筆記
- Vue學習筆記1Vue筆記
- Numpy學習筆記 1筆記
- HTML學習筆記1HTML筆記
- flex:1學習筆記Flex筆記
- Numpy學習筆記(1)筆記
- SLAM學習筆記(1)SLAM筆記
- Oracle學習筆記1Oracle筆記
- mysql學習筆記-1MySql筆記
- Zynq學習筆記(1)筆記
- scapy學習筆記(1)筆記
- Git—學習筆記1Git筆記
- perl學習筆記1筆記
- Oracle學習筆記-1Oracle筆記