記憶體池架構

來源:互聯網
上載者:User

標籤:c   記憶體管理   linux kernel   嵌入式   演算法   

TBOX的記憶體管理模型,參考了linux kernel的記憶體管理機制,並在其基礎上做了一些改進和最佳化。


記憶體整體架構



large_pool整個記憶體配置的最底層,都是基於large_pool的大塊記憶體配置池,類似於linux的基於page的分配管理,不過有所不同的是,large_pool並沒有像linux那樣使用buddy演算法進行(2^N)*page進行分配,這樣如果需要2.1m的記憶體,需要分配4m的記憶體塊,這樣力度太大,非常浪費。


因此large_pool內部採用N*page的基於page_size為最小粒度進行分配,因此每次分配頂多浪費不到一頁的空間。


而且如果需要的記憶體不到整頁,剩下的記憶體也會一併返回給上層,如果上層需要(比如small_pool),可以充分利用這多餘的部分記憶體空間,使得記憶體利用率達到最佳化。


而且根據tb_init實際傳入的參數需求,large_pool有兩種模式:


1.  直接使用系統記憶體配置介面將進行大塊記憶體的分配,並用雙鏈維護,這種比較簡單,就不多說了。
2.  在一大塊連續記憶體上進行統一管理,實現記憶體配置。


具體使用哪種方式,根據應用需求,一般的應用只需要使用方式1就行了,這個時候tb_init傳tb_null就行了,如果是嵌入式應用,需要管理有限的一塊記憶體空間,這個時候可以使用方式2, tb_init傳入指定記憶體空間地址和大小。


這裡就主要看下方式2的large_pool的記憶體結構(假設頁大小是4KB):


     --------------------------------------------------------------------------
    |                                     data                                 |
     --------------------------------------------------------------------------
                                         |
     --------------------------------------------------------------------------
    | head | 4KB | 16KB | 8KB | 128KB | ... | 32KB |       ...       |  4KB*N  |
     --------------------------------------------------------------------------


由於large_pool主要用於大塊分配,而超小塊的分配在上層small_pool中已經被分流掉了,所以這個應用中,large_pool不會太過頻繁的分配,所以片段量不會太大,為了進一步減少片段的產生,在free時候都會對下一個鄰近的空閑塊進行合并。而malloc在分配當前空閑塊空間不夠的情況下,也會嘗試對下一個鄰近空閑塊進行合并。


由於每個記憶體塊都是鄰近挨著的,也沒用雙鏈維護,沒有記憶體塊,都有個塊頭,合并過程僅僅只是改動記憶體塊頭部的size欄位,這樣的合并不會影響效率。


由於沒像buddy演算法那樣,用雙鏈維護空閑記憶體,雖然節省了鏈表維護的空間和時間,但是每次分配記憶體都要順序遍曆所有塊,來尋找閒置記憶體,這樣的效率實在太低了,為瞭解決這個問題,large_pool內部針對不同層級的塊,進行了預測,每次free或者malloc的時候,如果都會把當前和鄰近的空閑快,緩衝到對應層級的預測池裡面去,具體的分級如下:


     --------------------------------------
    | >0KB :      4KB       | > 0*page     | 
    |-----------------------|--------------
    | >4KB :      8KB       | > 1*page     | 
    |-----------------------|--------------
    | >8KB :    12-16KB     | > 2*page     | 
    |-----------------------|--------------
    | >16KB :   20-32KB     | > 4*page     | 
    |-----------------------|--------------
    | >32KB :   36-64KB     | > 8*page     | 
    |-----------------------|--------------
    | >64KB :   68-128KB    | > 16*page    | 
    |-----------------------|--------------
    | >128KB :  132-256KB   | > 32*page    | 
    |-----------------------|--------------
    | >256KB :  260-512KB   | > 64*page    | 
    |-----------------------|--------------
    | >512KB :  516-1024KB  | > 128*page   | 
    |-----------------------|--------------
    | >1024KB : 1028-...KB  | > 256*page   | 
     --------------------------------------


由於通常不會分配太大塊的記憶體,因此只要能夠預測1m記憶體,就足夠,而對於>1m的記憶體,這裡也單獨加了一個預測,來應對偶爾的超大塊分配,並且使得整體分配流程更加的統一。


如果當前層級的預測塊不存在,則會到下一層級的預測塊中尋找,如果都找不到,才回去遍曆整個記憶體池。


實際測試下,每個塊的預測成功基本都在95%以上,也就說大部分情況下,分配效率都是維持在O(1)層級的。

small_pool
小塊記憶體配置池


在上層每次調用malloc進行記憶體配置的時候,回去判斷需要多大的記憶體,如果這個記憶體超過或者等於一頁,則會直接從large_pool進行分配,如果小於一頁,則會優先通過small_pool進行分配,small_pool針對小塊的記憶體進行了快取,並最佳化了空間管理和分配效率。


由於程式大部分情況下,都在使用小塊記憶體,因此small_pool對記憶體的分配做了很大的分流,使得large_pool承受的壓力減小,片段量減少很多,而small_pool內部由雩都是由fixed_pool來對固定大小的記憶體進行管理,是不會存在外部片段的。而小塊記憶體的粒度本身就很小,所以內部片段量也相當少。


small_pool中的fixed_pool,就像是linux kernel中的slub,在small_pool中總共有12層級的fixed_pool,每個層級分別管理一種固定大小的記憶體塊,具體層級如下:


     --------------------------------------
    |    fixed pool: 16B    |  1-16B       | 
    |--------------------------------------|
    |    fixed pool: 32B    |  17-32B      |  
    |--------------------------------------|
    |    fixed pool: 64B    |  33-64B      | 
    |--------------------------------------|
    |    fixed pool: 96B*   |  65-96B*     | 
    |--------------------------------------|
    |    fixed pool: 128B   |  97-128B     |  
    |--------------------------------------|
    |    fixed pool: 192B*  |  129-192B*   |  
    |--------------------------------------|
    |    fixed pool: 256B   |  193-256B    |  
    |--------------------------------------|
    |    fixed pool: 384B*  |  257-384B*   |  
    |--------------------------------------|
    |    fixed pool: 512B   |  385-512B    |  
    |--------------------------------------|
    |    fixed pool: 1024B  |  513-1024B   |  
    |--------------------------------------|
    |    fixed pool: 2048B  |  1025-2048B  |  
    |--------------------------------------|
    |    fixed pool: 3072B* |  2049-3072B* |  
     -------------------------------------- 


其中 96B, 192B,384B,3072B並不是按2的整數冪大小,這麼做主要是為了更加有效利用小塊記憶體的空間減少內部片段。

fixed_pool
顧名思義,fixed_pool就是用來管理固定大小的記憶體配置的,相當於linux中slub,而fixed_pool中又由多個slot組成,每個slot負責一塊連續的記憶體空間,管理部分記憶體塊的管理,類似linux中的slab, 每個slot由雙鏈維護,並且參考linux的管理機制,分為三種slot管理方式:

  1. 當前正在分配的slot
  2. 部分空閑slots鏈表
  3. 完全full的slots鏈表


具體結構如下:


    current:
         --------------
        |              |
     --------------    |
    |     slot     |<--
    |--------------|
    ||||||||||||||||  
    |--------------| 
    |              | 
    |--------------| 
    |              | 
    |--------------| 
    ||||||||||||||||  
    |--------------| 
    |||||||||||||||| 
    |--------------| 
    |              | 
     --------------  


    partial:


     --------------       --------------               --------------
    |     slot     | <=> |     slot     | <=> ... <=> |     slot     |
    |--------------|     |--------------|             |--------------|
    ||||||||||||||||     |              |             |              |
    |--------------|     |--------------|             |--------------|
    |              |     ||||||||||||||||             |              |
    |--------------|     |--------------|             |--------------|
    |              |     ||||||||||||||||             ||||||||||||||||
    |--------------|     |--------------|             |--------------|
    ||||||||||||||||     ||||||||||||||||             |              |
    |--------------|     |--------------|             |--------------|
    ||||||||||||||||     |              |             |              |
    |--------------|     |--------------|             |--------------|
    |              |     |              |             ||||||||||||||||
    --------------       --------------               --------------


    full:


     --------------       --------------               --------------
    |     slot     | <=> |     slot     | <=> ... <=> |     slot     |
    |--------------|     |--------------|             |--------------|
    ||||||||||||||||     ||||||||||||||||             ||||||||||||||||
    |--------------|     |--------------|             |--------------|
    ||||||||||||||||     ||||||||||||||||             ||||||||||||||||
    |--------------|     |--------------|             |--------------|
    ||||||||||||||||     ||||||||||||||||             ||||||||||||||||
    |--------------|     |--------------|             |--------------|
    ||||||||||||||||     ||||||||||||||||             ||||||||||||||||
    |--------------|     |--------------|             |--------------|
    ||||||||||||||||     ||||||||||||||||             ||||||||||||||||
    |--------------|     |--------------|             |--------------|
    ||||||||||||||||     ||||||||||||||||             ||||||||||||||||
     --------------       --------------               --------------


具體的分配演算法
  1. 如果當前slot中還有閒置塊,優先從當前slot進行分配
  2. 如果當前slot中沒有空閑塊,則把這個slot放到full鏈表中去
  3. 從部分空閑slot鏈表中,挑一個閒置slot進行分配,並把它設為當前分配狀態。


具體的釋放演算法

  1. 釋放後如果這個slot完全空閑了,並且不是正在分配的slot,則把整個slot釋放掉,這樣既可以保證有一個可以分配的slot之外,還極大的降低了記憶體使用量,也避免某些情況下頻繁的釋放分配slot。
  2. 如果釋放的slot屬於full鏈表並且變為了部分空閑,則把這個slot移到部分空閑slot鏈表中去。


額外要提一下的是:


large_pool每次分配一塊空間給一個slot的時候,殘留下來的部分剩餘空間(<1*page), 也能直接返回給slot,讓slot充分利用這部分資料,這樣可以可以切分出更多地記憶體塊。


例如: 


fixed_pool每次增長一個包含256個32B記憶體塊的slot(需要8192B大小+16B內部資料維護大小),其實在用large_pool分配的時候,需要8208B的大小,由於需要按頁對齊(4KB),實際分配確佔用了`8192+4096: 12288B`的大小的空間。


但是large_pool支援把所有空間資料一併返回給上層,這樣slot其實擷取到了一個12288B大小的記憶體,並且也知道其實際大小為:12288B,因此實際切分了`(12288-(32B的slot內部維護資料))/32`也就是383個記憶體塊。 


多維護了127個記憶體塊,充分把large_pool的內部片段也利用上了,進一步增加了記憶體利用率。


fixed_pool中的slot
雖然類比與linux中的slab,但是其資料結構確跟slab不太一樣,它並沒有像slab那樣,對每個空閑小塊都用鏈表維護,而是直接用位段來維護是否閒置資訊,這樣更加節省記憶體,而且通過最佳化演算法,其分配效率和slab幾乎一樣。


在fixed_pool的slot的頭部,專門有一小塊獨立的資料,用於維護每個小塊的空閑資訊,每個塊只暫用一位元位的資訊,來判斷這個塊是否空閑,由於沒有記憶體塊都是固定大小的,所以位元位的位置定位,完全可以通過索引計算得到。


而且每次釋放和分配,都會去緩衝一個雙字大小的位資訊端,來預測下一次的分配,由於是雙字大小,總共有32個位元位,所以每次緩衝,最多可以預測鄰近32個記憶體塊。因此大部分情況下,預測成功率一直都是>98%的,分配效率都維持在O(1),比起large_pool的預測率還高很多,所以small_pool對large_pool的分流,還在一定程度上,進一步提高了記憶體配置效率。


而就算很倒黴,沒預測成功,slot的順序遍曆來尋找空閑快的演算法,也相當高效,完全是高度最佳化的,下面就詳細描述下。

slot的順序遍曆分配演算法最佳化
我們這裡主要用到了gcc的幾個內建函數:

  1. __builtin_clz:計算32位整數前置0的個數
  2.  __builtin_ctz:計算32位整數後置0的個數
  3. __builtin_clzll:計算64位整數前置0的個數
  4. __builtin_ctzll:計算64位整數後置0的個數


其實這四個類似,我們這裡就拿第一說明好了,為什麼要使用__builtin_clz呢?其實就是為了在一個32位端裡面,快速尋找某個空閑位的索引,這樣就能快速定位某個空閑塊的位置了。


比如有一個32位的位段資訊整數:x,計算對應空閑位0的索引,主需要:`__builtin_clz(~x)`


簡單吧,由於__builtin_clz這些內建函數,gcc用彙編針對不同平台高度最佳化過的,計算起來相當的快,那如果不是gcc的編譯器怎麼辦呢?


沒關係,我們可以自己用c實現個最佳化版本的,當然完全可以彙編繼續最佳化,這裡就先給個c的實現:


    static __tb_inline__ tb_size_t tb_bits_cl0_u32_be_inline(tb_uint32_t x)    {        // check        tb_check_return_val(x, 32);        // done        tb_size_t n = 31;        if (x & 0xffff0000) { n -= 16;  x >>= 16;   }        if (x & 0xff00)     { n -= 8;   x >>= 8;    }        if (x & 0xf0)       { n -= 4;   x >>= 4;    }        if (x & 0xc)        { n -= 2;   x >>= 2;    }        if (x & 0x2)        { n--;                  }        return n;    }



說白了,就是每次對半開,來減少判斷次數,比起每次一位一位的枚舉遍曆,這種已經是相當高效了,更何況還有__builtin_clz呢。


接下來就看下具體的遍曆過程:

  1.  按4/8位元組對齊位段的起始地址
  2.  每次按4/8位元組遍曆位段資料,遍曆過程利用cpu cache的大小,針對性的做迴圈展開,來最佳化效能。
  3.  通過判斷 !(x + 1) 來快速過濾 0xffffffff 這些已經滿了的位段,進一步提高遍曆效率。
  4.  如果某個位段不是0xffffffff,則通過__builtin_clz(~x)計算實際的空閑塊索引,並進行實際的分配。
  5.  最後如果這個的32位的位段沒有被分配滿,可以把它進行緩衝,來為下次分配做預測。

string_pool

講到這,TBOX的記憶體池管理模型,基本算是大概講完了,這裡就簡單提下string_pool,即:字串池


string_pool主要針對上層應用而言的,針對某些頻繁使用小型字串,並且重複率很高的模組,就可以通過string_pool進行最佳化,進一步減少記憶體使用量,string_pool內部通過引用計數+雜湊表維護,針對相同的字串只儲存一份。


例如可以用於cookies中字串維護、http中header部分的字串維護等等。。


----------


TBOX項目詳情:http://www.oschina.net/p/tbox
TBOX項目源碼:https://github.com/waruqi/tbox
TBOX項目文檔:https://github.com/waruqi/tbox/wiki/%E7%9B%AE%E5%BD%95

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.