stl第二級空間配置器詳解(1)

       SGI STL考慮到小型內存區塊的碎片問題,設計了雙層級配置器,第一級配置直接使用malloc()和free();第二級配置器則視狀況採用不一樣的策略,當配置區大於128bytes時,直接調用第一級配置器;當配置區塊小於128bytes時,遍不借助第一級配置器,而使用一個memory pool來實現。到底是使用第一級配置器仍是第二級配置器,由一個宏定義來控制。SGI中默認使用第二級配置器。函數

       第一級配置器實現的比較簡單,調用malloc()申請內存,申請失敗的時候,將拋出bad_alloc異常。下邊我着重介紹下二級配置器的實現思路。spa

      上邊提到了第二級配置器視狀況不一樣而採用不一樣的策略;其主要目的是避免太多小額區塊形成內存的碎片。一般狀況下,當配置區大於128bytes時,配置器視之爲「足夠大」而直接調用一級配置器,當配置區塊小於128bytes時,就之內存池來管理,具體作法是:設計

        sgi二級配置器會將任何小額區塊的內存需求量上調至8的倍數,(例如需求是30bytes,則自動調整爲32bytes),而且在它內部會維護16個free-list, 各自管理大小分別爲8, 16, 24,…,128bytes的小額區塊,這樣當有小額內存配置需求時,直接從對應的free list中拔出對應大小的內存(8的倍數);當客戶端歸還內存時,將根據歸還內存塊的大小,將須要歸還的內存插入到對應free list的最頂端。以下圖:指針

        

                                                                                                                    圖1對象

      那麼什麼是free-list呢?blog

      首先咱們看下free-list的定義內存

      上邊free-list節點定義得很是巧妙,與普通鏈表節點採用struct不一樣,這裏採用的是union;主要緣由是因爲爲了維護free-list鏈表,每一個節點須要額外的指針(指向下一個節點),這樣就會形成一種內存浪費。爲了不這種負擔,因此採用的union,根據union的特色,從第一個字段看,obj能夠看做一個指針,指向鏈表中的下一個節點;從第二個字段看,obj也能夠視爲一個指針,不過是指向實際的內存區,如圖1所示,這種一物兩用的結果,就是不會爲了維護鏈表所必須的指針而形成內存浪費。(由於stl容器是保存對象的,因此其自身信息固然要求是儘量的少佔用內存)。io

      下邊咱們根據一個圖來說下第二級空間配置器分配及歸還內存區塊的過程:class

      內存分配:thread

     

 圖2

            如上,咱們要申請96bytes的內存(因爲其內部有自動調整機制,因此有可能89~95bytes的申請也會自動上調到96bytes), 首先肯定須要在第幾號free-list中獲取,如圖示是第11號free-list my_free_list,而後獲取這個第一個元素(即第一塊內存)的地址賦值給result,再調整my_free_list讓其指向下一個區塊。

           內存歸還:


圖3

跟配置內存相似,一樣是先尋找對應的free list,而後將要歸還的內存塊插入到對應free list的頭部。
不過在內存配置的過程當中,咱們還須要處理這種狀況:當對應free list沒有可用區塊時,就須要給對應的free list從新填充內存。以下圖:
圖4 

         

//傳回一個大小爲 n的對象,而且有時候會爲適當的freelist增長節點. 
//假設 n已經適當上調至 8的倍數。
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; 
第 2 章空間配置器(allocator)
// 若是隻得到一個區塊,這個區塊就撥給呼叫者用,free list無新節點。
 if (1 == nobjs) return(chunk); 
// 不然準備調整 free list,歸入新節點。
 my_free_list = free_list + FREELIST_INDEX(n); 
// 如下在 chunk空間內創建freelist
 result = (obj *)chunk; //這一塊準備傳回給客端
// 如下導引 free list指向新配置的空間(取自記憶池)
 *my_free_list = next_obj = (obj *)(chunk + n); 
// 如下將 free list 的各節點串接起來。
 for (i = 1; ; i++) {//從 1 開始,由於第 0 個將傳回給客端
 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); 
} 

        refill()函數完成的主要功能是:根據所須要申請的區塊的大小n,調用chunk_alloc(n, nobjs); 默認申請nobjs個區塊(默認爲20,又可能不足20,nobjs是引用傳遞);若是申請的區塊只有一個,那麼直接返回,free list仍舊無可用區塊,若是大於1,那麼將第一個chunk塊做爲返回值,其他的chunk按照n劃分爲free list的節點,並將其串接到free list中區。最後free list頭節點會指向到新分配的chunk。

        綜上,我便將stl二級空間配置的空間的配置,回收,以及從新填充大小詳細介紹了一遍,下篇將繼續介紹內存池的設計。

相關文章
相關標籤/搜索