STL in C++11 (Allocator 4)

我們在之前的文章學(xué)習(xí)了編寫malloc_allocator膝但,它是一個借助malloc分配內(nèi)存的分配器冲九,并且實現(xiàn)了在C++11標準中的接口。同時malloc_allocator可以幫助我們實現(xiàn)另一個分配器free_list_allocator跟束。

free_list_allocator是我們整個項目真正要使用的分配器莺奸。在說明具體的代碼之前,先來說說為什么要用分配器而不直接使用 new 與 delete 進行內(nèi)存的分配與釋放冀宴。如果有學(xué)習(xí)過操作系統(tǒng)的相關(guān)知識的話應(yīng)該有所了解: 頻繁的進行內(nèi)存申請時將產(chǎn)生大量的內(nèi)存碎片且降低性能灭贷。

舉個例子, 假設(shè)我們有10個單位的連續(xù)內(nèi)存區(qū)域略贮,范圍 0 - 9甚疟。我們首先申請 4 個單位的空間, 那么將分配區(qū)域 0 - 3逃延。然后我們再申請 4 單位內(nèi)存览妖, 分配區(qū)域 4 - 7。現(xiàn)在我們只有8 - 9的位置是空閑的揽祥, 然而如果以后申請的內(nèi)存都大于 2 個單位, 那么只能從別的內(nèi)存段分配內(nèi)存讽膏,區(qū)域 8 - 9永遠用不上了,成為碎片拄丰。

為了避免碎片的產(chǎn)生府树,我們才使用分配器定義容器的分配策略。那么分配器采用什么方法避免內(nèi)存碎片的產(chǎn)生呢料按?malloc_alloctor雖說是分配器奄侠,但它只是簡單的封裝了malloc()函數(shù)進行內(nèi)存分配而已.。 malloc()對偶發(fā)的大量內(nèi)存分配做了優(yōu)化站绪,所以此時malloc_alloctor的效率良好。然而在需要頻繁分配少量內(nèi)存的情況下效率低下丽柿,且容易產(chǎn)生內(nèi)存碎片恢准。

有鑒于此,在這一情況下甫题,人們常使用基于內(nèi)存池的分配器來解決頻繁少量分配問題馁筐。與默認的“按需分配”方式不同,在使用基于內(nèi)存池的分配器時坠非,程序會預(yù)先為之分配大塊內(nèi)存(即“內(nèi)存池”)敏沉,而后在需要分配內(nèi)存時,自定義分配器只需向請求方返回一個指向池內(nèi)內(nèi)存的指針即可;而在對象析構(gòu)時盟迟,并不需實際解除分配內(nèi)存秋泳,而是延遲到內(nèi)存池的生命周期完結(jié)時才真正解除分配。這樣不僅能減少內(nèi)存碎片的產(chǎn)生也避免了對系統(tǒng)頻繁的內(nèi)存申請攒菠。

我們要實現(xiàn)free_list_allocator正是此類分配器迫皱。free_list_allocator的整體思想是: 如果要分配的內(nèi)存大于 128 bytes(即分配大量內(nèi)存) ,就交給malloc_allocator處理辖众。否則由 memory pool(內(nèi)存池) 負責(zé)分配與回收卓起。

我們先來了解free_list_allocator的聲明:

template<typename T>
class free_list_allocator
{
private:
    static const std::size_t ALIGN = 8;
    static const std::size_t MAX_BLOCK_SIZE = 128;
    static const std::size_t FREE_LIST_NUM = MAX_BLOCK_SIZE / ALIGN;

    struct obj
    {
        obj *free_list_link;
    };

    static obj *free_list[FREE_LIST_NUM];
    //內(nèi)存池起始位置
    static char *start_pool;
    //內(nèi)存池結(jié)束位置
    static char *end_pool;
    static std::size_t heap_size;


    //將byte上調(diào)至8的倍數(shù)
    static std::size_t round_up(std::size_t byte)
    {
        return ((byte) + ALIGN - 1) & ~(ALIGN - 1);
    }

    //由所需內(nèi)存大小決定free_list的索引
    static std::size_t free_list_index(std::size_t byte)
    {
        return  byte / (ALIGN + 1);
    }

    //為free_list加入新的塊
    static T *refill(std::size_t block_size);

    //分配 num 個 block_size 的空間
    static char *chunk_alloc(std::size_t block_size, std::size_t& num);


public:
    typedef T value_type;

    free_list_allocator() {}
    template<typename U>
    free_list_allocator(const free_list_allocator<U>&) {}

    static T *allocate(std::size_t num);
    static void deallocate(T *p, std::size_t num);
};


template<typename T>
typename free_list_allocator<T>::obj *
free_list_allocator<T>::free_list[FREE_LIST_NUM] = {
    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
};


template<typename T>
char *free_list_allocator<T>::start_pool = nullptr;

template<typename T>
char *free_list_allocator<T>::end_pool = nullptr;

template<typename T>
std::size_t free_list_allocator<T>::heap_size = 0;

free_list_allocator的核心有兩部分: free_list 與 memory pool, free_list 從 memory pool 中獲取空間并添加內(nèi)存塊凹炸。那么所謂的free_list到底是什么戏阅?

struct obj
{
    obj *free_list_link;
};

static obj *free_list[FREE_LIST_NUM];

free_list 被聲明為一個大小為 FREE_LIST_NUM 的數(shù)組, 且數(shù)組元素為一個指向 struct obj 的指針啤它。實際上每個 free_list 的元素分別指向區(qū)塊大小為 8, 16, 24, 32, 40, 48 ..... 120, 128 的鏈表奕筐。

free_list 示意圖

上圖只顯示了8個free_list(實際有16個)。按照之前所說蚕键,3號free_list 指向大小為 32 bytes 的內(nèi)存塊救欧。其中每個內(nèi)存塊的開始部分儲存一個 obj 結(jié)構(gòu),每個 obj 都有一個指向下一個內(nèi)存塊的指針(即 free_list_link)锣光。每當用戶需要申請內(nèi)存時笆怠, free_list_allocator 便從free_list中找到相應(yīng)大小的塊并且以返回內(nèi)存塊首地址的方式分配出去(即 obj 的地址)。

再了解了 free_list_allocator 的基本原理后誊爹,我們終于能看看具體實現(xiàn)了蹬刷。

  1. free_list_index(std::size_t byte)
//由所需內(nèi)存大小決定free_list的索引
static std::size_t free_list_index(std::size_t byte)
{
    return  byte / (ALIGN + 1);
}

free_list_index 用來查找目標 free_list 的索引。比如需要 20 字節(jié)的內(nèi)存時我們應(yīng)該去 free_list[ 2 ] 中取得內(nèi)存塊频丘。

  1. allocate(std::size_t num)
template<typename T>
T *free_list_allocator<T>::allocate(std::size_t num)
{   
    if (num * sizeof(T) > MAX_BLOCK_SIZE) {
        return malloc_allocator<T>::allocate(num);
    }

    obj *result;
    auto target_free_list = free_list + free_list_index(num * sizeof(T));
    result = *target_free_list;
    
    if (result == nullptr) {
        return refill(round_up(num * sizeof(T)));
    }

    //將free_list內(nèi)的元素指向第2個內(nèi)存塊
    *target_free_list = result->free_list_link;
    return reinterpret_cast<T*>(result);
}

同樣办成, allocate(num) 負責(zé)分配 num 個元素的內(nèi)存。 它首先計算所需總內(nèi)存是否大于最大塊的大新(128 bytes)迂卢,如果是的話就調(diào)用 malloc_allocator<T>::allocate 分配內(nèi)存。想想之前提過的桐汤, malloc 適合進行大量內(nèi)存的分配而克。然后借助 free_list_index 找到具有合適大小的塊的 free_list。

接著對target_free_list解除引用獲取第一個內(nèi)存塊的地址怔毛。如果 result == nullptr, 則說明此 free_list 為空员萍,沒有指向的內(nèi)存塊。此時我們需要調(diào)用 refill() 重新為這個 free_list 添加內(nèi)存塊拣度。(refill 的具體實現(xiàn)要留到下次了)

如果我們成功獲取第一個內(nèi)存塊的地址話碎绎, 我們還需要將 free_list 的指針重新設(shè)置為第二個內(nèi)存塊的地址(這個時候第二個內(nèi)存塊就變成第一個了)螃壤。最后再類型轉(zhuǎn)換返回結(jié)果就行了。

調(diào)用 allocate 前
調(diào)用 allocate 后
  1. deallocate(T *p, std::size_t num)
template<typename T>
void free_list_allocator<T>::deallocate(T *p, std::size_t num)
{
    /*
    p為需要回收的內(nèi)存區(qū)域的首指針筋帖,num代表區(qū)域內(nèi)元素的個數(shù)
    */

    std::size_t size = num * sizeof(T);
    if (size > MAX_BLOCK_SIZE) {
        malloc_allocator<T>::deallocate(p, num);
        return;
    }
    
    //被回收的內(nèi)存將被加入target_free_list指向的鏈表
    auto target_free_list = free_list + free_list_index(size);
    obj *target_block = reinterpret_cast<obj*>(p);
    target_block->free_list_link = *target_free_list;
    *target_free_list = target_block;
}

deallocate 的思想與 allocate 對應(yīng):allocate 是獲取第一個內(nèi)存塊奸晴,將 free_list 指向第二個內(nèi)存塊。而 deallocate 把需要回收的內(nèi)存塊指向當前第一個內(nèi)存塊幕随,將free_list 指向這個被回收的塊蚁滋。

調(diào)用 deallocate 前
調(diào)用 deallocate 回收后

我們現(xiàn)在已經(jīng)可以使用 allocate 與 deallocate 分配釋放內(nèi)存,但還有 refill()和 chunk_alloc() 沒有實現(xiàn)赘淮。限于文章的篇幅辕录,那就只能等到下次了。再見~

下一篇: STL in C++11 (Allocator 5)

最后附上本篇文章的代碼地址:free_list_allocator.h

以及github地址:https://github.com/Puppas/STL-in-Cpp11

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梢卸,一起剝皮案震驚了整個濱河市走诞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛤高,老刑警劉巖蚣旱,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異戴陡,居然都是意外死亡塞绿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門恤批,熙熙樓的掌柜王于貴愁眉苦臉地迎上來异吻,“玉大人,你說我怎么就攤上這事喜庞【骼耍” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵延都,是天一觀的道長雷猪。 經(jīng)常有香客問我,道長晰房,這世上最難降的妖魔是什么求摇? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮殊者,結(jié)果婚禮上与境,老公的妹妹穿的比我還像新娘。我一直安慰自己幽污,他們只是感情好嚷辅,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布簿姨。 她就那樣靜靜地躺著距误,像睡著了一般簸搞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上准潭,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天趁俊,我揣著相機與錄音,去河邊找鬼刑然。 笑死寺擂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的泼掠。 我是一名探鬼主播怔软,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼择镇!你這毒婦竟也來了挡逼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤腻豌,失蹤者是張志新(化名)和其女友劉穎家坎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吝梅,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡虱疏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了苏携。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片做瞪。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖兜叨,靈堂內(nèi)的尸體忽然破棺而出穿扳,到底是詐尸還是另有隱情,我是刑警寧澤国旷,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布矛物,位于F島的核電站,受9級特大地震影響跪但,放射性物質(zhì)發(fā)生泄漏履羞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一屡久、第九天 我趴在偏房一處隱蔽的房頂上張望忆首。 院中可真熱鬧,春花似錦被环、人聲如沸糙及。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浸锨。三九已至唇聘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柱搜,已是汗流浹背迟郎。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留聪蘸,地道東北人宪肖。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像健爬,于是被迫代替她去往敵國和親控乾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容