以stl運用的角度而言,空間配置器是最不需要介紹的,它總是藏在一切元件的背後,默默工作。整個stl的操作物件都存放在容器之中(vertor、list),而容器一定需要配置空間以放置資料,這就是空間配置器的作用。
雖然stl提供了讓我們自定義空間配置器的介面,但是不建議自己定義,因為標準提供的空間配置器是安全的,且效率也不錯的。所以我們使用時,一般都會使用預設的配置器。如下:
template >
class vector {};
vectvec; //這裡只傳入int型別,使用預設的空間配置器
下面的空間配置器是按照sgi 版本的stl進行講解的,但是stl的原理是通的。
通過前面整理c++ new和delete的詳解,我們知道c++記憶體配置操作和釋放操作是這樣的:
class foo ;
foo* pf = new foo; //配置記憶體,然後構造物件
delete pf; //將物件析構,然後釋放記憶體
這其中的 new 內含兩個階段操作:1、呼叫operator new 配置記憶體。2、呼叫建構函式,構造物件內容
delete也內含兩個階段操作:1、呼叫析構函式。2、呼叫operator delete 釋放記憶體。
為了精密分工,stl 將這兩個階段操作區分開來。記憶體配置操作由 成員函式 alloccate() 負責,記憶體釋放由 deallcate() 負責;物件構造由 construct() 負責,物件析構則由 destroy() 負責。
在記憶體分配的過程中,會有幾個問題需要考慮。
1、小塊記憶體帶來的記憶體碎片問題。
2、小塊記憶體頻繁申請釋放帶來的效能問題。
為了解決這些問題,sgi stl設計了 雙層級配置器,也就是第一級配置器和第二級配置器。第一級配置器直接使用 malloc() 和 free() ,第二級配置器則視情況採用不同的策略:當配置區塊超過128 bytes 時,視之為 「足夠大」,便呼叫第一級配置器;當配置區塊小於 128 bytes 時,視之為 「過小」 ,為了降低額外負擔,便採用複雜的 記憶體池 管理方式。
第一級配置器的流程如下:
sgi的第一級配置器以 malloc(), free(), realloc() 等c函式執行實際的記憶體配置、釋放、重配置操作。當 malloc 或者 realloc 呼叫不成功後,改呼叫 oom_malloc() 和 oom_realloc() 。後兩者都有內迴圈,不斷呼叫「記憶體不足處理例程」,期望在某次呼叫之後,獲得足夠的記憶體。但如果「記憶體不足處理例程」未被客戶端設定,則直接丟擲 bad_alloc 異常,或者終止程式。
注意:設計記憶體不足處理例程是客戶端的責任,設定記憶體不足處理例程也是客戶端的責任。
二級配置器使用記憶體池+自由鍊錶的形式避免了小塊記憶體帶來的碎片化,提高了分配的效率,提高了利用率。它是用乙個16個元素的自由鍊錶(free_list)來管理的,每個位置的記憶體大小都是8的倍數,分別為:8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128。
free_list的節點結構如下:
union obj
;
使用union是為了節省記憶體,這樣每個節點就不需要額外的指標。
記憶體池與自由陣列 free_list 之間的關係如下圖所示:
其中free_list的第乙個元素指向 8個位元組的空間,8個位元組的空間我給分配了10個。free_list的最後乙個元素指向128個位元組的空間,此空間我給分配了4個。
free_list管理的是記憶體池中已經分配給 free_list 且尚未使用的記憶體,如果系統想從free_list中拿一8位元組記憶體,則直接從free_list[0]中彈出頂部第乙個元素,然後頂部後移。
主要分為四種情況:
1、free_list列表中有空餘記憶體。如果申請3個位元組的記憶體,則所需空間大小提公升為8的倍數,然後去 free_list 中查詢相應的鍊錶,如果 free_list[i] 不為空,則返回第乙個元素,然後把頭指標往後移。
2、free_list 列表中沒有空餘,但記憶體池不為空。首先檢驗記憶體池中的大小是不是比申請的記憶體大,比如申請20*8的記憶體,如果足夠,則分配相應記憶體,將其中乙個分配給使用者使用,其它的掛在相應的 free_list 中。如果記憶體池不夠大,只夠幾個記憶體分配,則就分配這幾個,把相應的資料返回。如果連乙個都不夠則執行第三中情況。
3、free_list列表中沒有空餘,記憶體池也不夠。呼叫malloc重新分配記憶體,分配時會多分配一倍的記憶體,把相應的記憶體掛到free_list下,剩餘的放到記憶體池中。
4、free_list列表中沒有空餘,記憶體池也不夠,malloc也失敗。則呼叫一級空間配置器,裡面會有迴圈處理,或者丟擲異常。
當使用者從二級空間配置器中申請的記憶體被釋放時,二級空間配置器將**的記憶體插入到對應的 free_list 中。其流程如下:
我們知道,引入相對複雜的空間配置器,主要源自兩點:
1、頻繁使用malloc、free開闢釋放小塊記憶體帶來的效能效率的低下
2、記憶體碎片問題,導致不連續記憶體不可用的浪費
引入兩層配置器幫我們解決了以上的問題,但是也帶來一些問題:
1、記憶體碎片的問題,自由鍊錶所掛區塊都是8的整數倍,因此當我們需要非8倍數的區塊,往往會導致浪費。
2、我們並沒有釋放記憶體池中的區塊。釋放需要到程式結束之後。這樣子會導致自由鍊錶一直占用記憶體,其它程序使用不了。
stl下的空間配置器分位兩級,他們沒有高低之分,只有乙個條件,當使用者所需要的的記憶體大小:
1.大於128位元組時,交給一級配置器處理
2.小於等於128位元組時,交給二級配置器處理
一級配置器的實現思想:實際就是對c下的malloc,relloc,free進行了封裝,並且在其中加入了c++的異常。
我們要了解對於當記憶體不足呼叫失敗後,記憶體不足處理是使用者需要解決的問題,stl不處理,只是在你沒有記憶體不足處理方法或有但是呼叫失敗後丟擲異常。
二級配置器的實現思想:
二級配置器是通過記憶體池和空閒鍊錶配合起來的乙個特別精巧的思想。
空閒鍊錶:它每個節點分別維護8,16,32.。。。128位元組以8的倍數的各記憶體大小。
它是這樣做的,首先,使用者申請記憶體小於128個位元組,進入二級配置器程式,假如第一次使用者申請記憶體為32位元組,程式直接呼叫s_chunk_alloc,直接申請40個大小的32位元組空間,乙個交給客戶,另外19個交給空閒鍊錶,剩下的20個交給記憶體池。
接下來,第二次申請空間,假設這次使用者需要64位元組的空間,首先,程式檢查64位元組的空閒鍊錶節點是否有空閒空間,如果有,直接分配,如果沒有,就去記憶體池,然後檢查記憶體池的大小能分配多少個自己大小的節點,完全滿足,則拿出乙個給使用者,剩下的19個給空閒鍊錶,如果不夠,盡可能分配記憶體池的最大個數給使用者和空閒鍊錶。如果乙個都分配不出,首先把記憶體池中的剩下沒用的小記憶體分給相應適合的空閒鍊錶,然後繼續呼叫s_chunk_alloc申請記憶體,記憶體申請不足,去檢索空閒鍊錶有沒有尚未使用的大的記憶體塊,如果有,就拿出來給給記憶體池,遞迴呼叫s_chunk_alloc,如果空閒鍊錶也沒有了,則交給一級介面卡(因為其有對記憶體不足處理的方法)。記憶體夠,則拿到申請的記憶體補充記憶體池。重複上面的操作,將乙個給客戶,其他的19個交給空閒鍊錶,剩下的給記憶體池。
二級配置器實現了對小記憶體的高效管理,使內部碎片出現的概率大大降低,個人見解,二級介面卡差不多已經達到了對小記憶體接近完美的處理。
空間配置器
普通存放資料的原理 需要空間 new new 申請空間 構造物件 new是將malloc重新封裝的,使用一次malloc,在記憶體中除了會開闢所需空間外,還會額外開闢36個位元組 通過這種形式管理空間可以防止越界訪問 普通方式存放資料是存在缺陷的 1.頻繁的向系統索要小的記憶體塊,會產生記憶體碎片。...
SGI STL空間配置器和記憶體池
最近在看侯捷老師的 stl原始碼剖析 非常感嘆其中空間配置器實現的巧妙和細緻,對效率真正是錙銖必較。一般我們所習慣的記憶體配置和釋放是通過new和delete來完成的,而new運算包含了兩個階段 1.呼叫 operator new配置記憶體 2.呼叫建構函式 foo 構造物件。delete運算也包含...
stl記憶體池剖析空間配置器
原 參考stl庫 具體只有記憶體分配 類似於c 的newhander 部分 沒有列出 stl空間配置器的記憶體池模型 pragma once include include include using namespace std class mallocalloctemplate 定義一級空間配置器...