STL空間配置器讀書筆記

STL中的空間配置器讀書筆記

SGI-STL中定義了兩套空間配置器,分別是std::allocator和std::alloc。
std::allocator只是在::operator new和::operator delete上做了一層簡單的封裝。所以一般也不會使用allocator,而alloc則是一套相比allocator在設計上更符合規範的組件。

這裏有個點需要提及。
全局的::operator new/delete和C++中的關鍵字new/delete。
兩者看起來是基本沒有區別的,至少在名稱上。實際上的功能則不相同。
我們在new一個對象的時候,在一個對象構造的出生過程中會發生以下兩個操作。

class A{}
A* pa=new A;

關鍵字new會先調用::operator new進行內存的申請,然後調用類A的構造函數A::A()完成對象的內容初始化,通常使用者會在構造函數中進行一些成員必要的初始化工作,就是在這一步中起作用的,當然編譯器也會替我們完成一些默認的工作,如:爲int這種基本類型的成員初始化爲默認值,如果我們沒有爲構造函數添加內容。
同理delete關鍵字

delete pa;

會先執行類A的析構函數,在調用::operator delete釋放內存。在window系統中以及SGI-STL中的::operator new/delete是通過malloc/free來進行內存分配和釋放的工作的。

下面則是對std::alloc的一些關鍵地方進行記錄和理解。
alloc把內存分配->對象構造->對象析構->內存釋放這4個過程分開爲4個操作分別負責。
對應關係爲:
內存分配 ——alloc::allocate()
對象構造 ——alloc::construct()
對象析構 ——alloc::deallocate()
內存釋放 ——alloc::destroy()

SGI的內存分配策略中使用了兩級配置器,當對象申請內存區塊大小大於128bytes的時候,會直接調用一級配置器,一級配置器直接調用malloc直接進行分配。
一級配置器實現上基本是對malloc和free的一層封裝,並且加上了一個handler機制來處理內存不足的情況,handler機制主要是在malloc申請內存失敗的時候調用一個_S_oom_malloc()函數來處理。
這裏寫圖片描述

相對應的還有realloc操作也是類似的策略。

如果申請的是比較小的對象,大小不大於(<=)128bytes時,會使用二級配置器進行分配

爲了處理小區塊造成內存的碎片的問題,二級配置器使用了比較複雜的策略,簡單的說由自由鏈表和內存池機制組成。

二級配置器的邏輯,當申請的區塊大小大於128bytes時,直接由一級配置器負責分配內存。反之,會去自由鏈表中查找適合的分塊。
自由鏈表是由下面這個聯合體結構構成的一個大小爲16的鏈表數組。

union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };

這裏寫圖片描述

數組中的元素從下標0~15依次負責維護一條區塊大小爲8,16,24,32…128(以8爲倍數)的內存鏈表。
即:
比如當我們申請一個大小爲12byte的對象時,則配置器會向上調整至8的倍數,去維護16字節區塊的鏈表(free_list[1])中尋找可用內存,並返回一個指向大小爲16字節的空間的起始地址的void*指針(這當中有4個字節在使用中是浪費掉了的)。

那麼問題來了,初始的時候自由鏈表應該是沒有區塊的,那麼區塊又是從哪裏來的呢?
這裏就要說說內存池的機制了。
下圖可見當鏈表的區塊用完後,將會執行_S_refill()函數將鏈表重新填充
這裏寫圖片描述

_S_resill()函數會通過_S_chunk_alloc()在內存池中抽取20塊相應大小的內存,如果只能抽取到一個可用區塊,則直接返回給用戶。反之,將第一塊返回給用戶,剩餘的鏈接到負責維護相應區塊大小的自由鏈表上。
這裏寫圖片描述

而在chunk_alloc()中才是對內存池的操作。
假設程序一開始,第一次調用了chunk_alloc(32,20),則會malloc申請40個32bytes的區塊,第一個返回給用戶,剩下19個用來鏈接到free_list3上,剩下的20塊區塊留給內存池中,接下來又有內存申請的請求且最終調用了chunk_alloc(64,20)這是free_list[7]沒有掛載內存區塊,則需要去向內存池請求內存,由於此時內存池只能夠供應10個64bytes的區塊,所以會全部返回,且第一個給用戶,剩下9個鏈接到free_list[7]上(注意20個區塊不是強制的,是儘可能的從內存池中拿20個區塊出來)。如果此時又發生chunk_alloc(96,20)的調用,此時free_list[11]和內存池都是空的,就會進行malloc內存申請,申請40+n個區塊,n的大小計算如下圖round_up()所示
這裏寫圖片描述
這裏寫圖片描述
然後,1個返回給用戶,19個鏈接到free_list[11]中,餘下20+n的大小留給內存池。

如果最終,系統的堆內存也耗盡了,malloc申請失敗,chunk_alloc就會到free_list中尋找可用且區塊足夠大的內存來使用,如果還是找不到的話就會交由一級配置器來配置內存,前面說到一級配置器也是使用malloc來申請內存的,但是裏面有內存不足的處理機制(oom),會盡可能的釋放一些內存以滿足當前使用,如果不行的話就會拋出bad_alloc異常。

最後總結一下:SGI_STL的空間配置器設計,首先分爲兩級配置器,申請區塊大於128bytes時調用一級配置器直接使用malloc分配和free釋放。小於128bytes時,調用二級配置器,分配自由鏈表數組和內存池來維護小型區塊的內存來供使用,大小爲16的自由鏈表數組(類型爲指向數組),每一個元素分別維護一個鏈表,每個鏈表依次負責維護8,16,24….128bytes大小的內存區塊。當自由鏈表沒有可用空間時,會向內存池請求內存,內存池沒有可用空間時會進行malloc申請空間。
所有的這些的操作都是爲了避免向操作系統申請過多小型區塊,造成系統堆內存碎片化,也就是說自由鏈表和內存池的內存在使用完畢後是不會歸還給系統的,而是返還給自由鏈表維護以備下次繼續用。最終實現的效果就是程序整體上向系統申請的都是大塊的內存,可以有效避免內存碎片化。但是因爲是在用戶態上進行內存管理,就容易出現程序運行時,單一進程對內存的使用上出現存在佔用過高,有效利用率低的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章