1. 程式人生 > >高效記憶體池的設計方案[C語言]

高效記憶體池的設計方案[C語言]

  • 作者:鄒祁峰
  • 郵箱:[email protected]
  • 日期:2012.11.18 凌晨02:00
  • 轉載請註明來自"祁峰"的CSDN部落格

1 引言

    本人在轉發的博文《記憶體池的設計和實現》中,詳細闡述了系統預設記憶體分配函式malloc/free的缺點,以及進行記憶體池設計的原因,在此不再贅述。通過對Nginx記憶體池以及《記憶體池的設計和實現》的分析後,現提出一種效能更優(申請/釋放記憶體時間複雜度為O(1))的記憶體池的設計方案。如有不妥之處,歡迎指正!如有其他的記憶體池的設計方案,歡迎共同分享和探討。

2 結構設計

2.1 記憶體池結構

/* 記憶體池結構體 */
typedef struct
{
    int unitsize;             /* 記憶體單元大小,即unit的大小 */
    int initnum;              /* 初始記憶體單元的數目 */
    int grownum;              /* 每次新增記憶體單元的數目 */
    int totalnum;             /* 記憶體單元總數 */
    memblock_t *block;        /* memblock_t連結串列頭 */
    char *idleunit;           /* 空閒記憶體單元連結串列頭 */
#if defined(__MEMPOOL_LOCK__)
    spinlock_t lock;          /* 自旋鎖:使用自旋鎖有效避免CPU切換的開銷 */
#endif /*__MEMPOOL_LOCK__*/
}mempool_t;

程式碼1 記憶體池結構

/* 記憶體塊結構體 */
typdef struct
{
    int unitnum;              /* 記憶體塊總數 */
    int idlenum;              /* 空閒記憶體塊數 */
    mempool_t *pool;          /* 所屬池:所屬mempool_t */
    char *lastunit;           /* 結束塊地址(此變數可刪除) */
    memblock_t *next;         /* 下一個memblock_t */
}memblock_t;

程式碼2 記憶體塊結構

/* 記憶體單元資訊 */
typedefstruct
{
    memblock_t *block;        /* 所屬塊:記憶體單元所屬memblock_t */
    char *next;               /* 下一塊記憶體塊地址 */
}memunit_info_t;

程式碼3 記憶體單元資訊

2.2 總體結構

記憶體池的總體結構圖為:

圖1 總體結構圖

2.3 執行機制

此記憶體池的執行機制如下:

  • 1)將每一個記憶體單元的大小固定化,可提高記憶體分配效率。比如:記憶體單元分別為:{8, 16, 32, 64, 128, 256, 512, 1024, …}(單位:byte)。Mempool_t之間是通過陣列形式組織的,其大體結構如下:(注:為了明確Mempool_t之間的關係,未標出其他變數之間的關係)

圖2 Mempool_t陣列

[注:為提高效率,通過陣列儲存Mempool_t,在申請記憶體空間時,可通過偏移量快速定位使用哪個大小的記憶體池]

  • 2)記憶體池實際可供分配的記憶體單元是在Memblock_t中,當所有Memblock_t中的記憶體單元被使用完後,則需申請開闢一個新的Memblock_t,並加入到Memblock_t連結串列之中。Memblock_t的組織方式為:(注:為了明確Memblock_t之間的關係,未標出其他變數之間的關係)

圖3 Memblock_t連結串列

  • 3)使用連結串列組織空閒記憶體單元,可大大提高記憶體分配/釋放時的效率(時間複雜度為O(1))。Mempool_t中的idleunit是空閒記憶體單元的連結串列頭。空閒記憶體單元的組織形式如下:(注:為明確空閒記憶體單元之間的關係,未標明其他變數之間的關係)

圖4 空閒記憶體單元連結串列

[說明:Memblock_t中的用紅色數字標記的記憶體單元代表已被分配記憶體單元

綠色數字標記的記憶體單元代表空閒記憶體單元]

  • 4)當申請記憶體時,將idleunit指向的記憶體單元踢出空閒記憶體單元連結串列,並idleunit指向記憶體單元的後繼,再返回該記憶體單元的地址。以圖4為例,申請記憶體塊後,空閒記憶體單元連結串列如圖所示:(注:請對比與圖4之間的變化)

圖5 記憶體申請圖

[注:當申請的記憶體空間size比所有的記憶體單元都大時,

則通過malloc()向OS申請size+sizeof(memunit_info_t)的記憶體空間]

  • 5)當釋放記憶體單元unitn時,將unitn的後繼改為idleunit的指向,同時將idleunit指向要釋放的記憶體單元unitn。以圖4為例,釋紅色數字標記的記憶體單元3後,空閒記憶體單元連結串列如圖所示:(注:請對比與圖4之間的變化)

圖6 記憶體釋放圖

  • 6)記憶體單元是通過連結串列形式進行組織管理的,因此,每個記憶體單元有額外的空間用來存放組織連結串列的資訊。將圖4進一步展開:(注:請結合圖4一起看)

圖7 記憶體單元內部結構

說明:

  -> 1. 每個記憶體單元的內部結構:memunit_info_t結構+unitsize大小的空間。每個記憶體單元的大小為:sizeof(memunit_info_t)+unitsize;

  -> 2. idleunit指向的是記憶體單元的data;空閒記憶體單元的next指向的是後繼記憶體單元的data,無後繼則為NULL;已分配的記憶體單元的next始終為NULL。

  -> 3. 記憶體單元的block指向宿主Memblock_t,這可快速的確定對當前記憶體單元屬於哪個Memblock_t,再通過Memblock_t中的pool,可快速獲知屬於哪個Mempool_t。

  -> 4. 在分配記憶體時,返回給使用者的是data的地址,而不是記憶體單元的地址。

  • 7)在釋放記憶體單元時,為使被釋放記憶體單元加入空閒記憶體單元連結串列,可通過記憶體單元的block獲知所屬Memblock_t,再通過pool獲知所屬Mempool_t,因此,便可知空閒記憶體單元連結串列頭idleunit,此時便可將被釋放的記憶體單元加入空閒連結串列。

圖8 所屬Mempool_t

2.4 優缺點

通過對以上幾點的分析,可知此記憶體池有以下優缺點:

優點:

  1. 定位記憶體池的時間複雜度為O(1)

    記憶體單元可申請使用的空間依次為8、16、32、64、128、256、512、1024 ..., 因此,定位記憶體池的演算法:(n為記憶體池陣列下標)

if(size > 8)
{
    shift=1;
    for(s=size-1; s>=1; ++shift) { NULL; } /* 計算位移 */
    n = shift - 3; /* 計算角標 */
}
else
{
    n = 0;
}
程式碼4 計算角標

  2. 申請和釋放記憶體的時間複雜度為O(1)

  3. 有效減少記憶體碎片

  4. 較小的互斥粒度:申請空間時,每次只鎖住對應的mempool_t的記憶體池,依然可以申請其他size的記憶體池空間[注:如果再加入為每個執行緒分配一個記憶體池物件的機制,則可達到零互斥零競爭。這樣的話,可不使用互斥機制,同時能夠進一步提高效能]

  5. 記憶體空間可動態擴充套件。

缺點:

  1. 記憶體單元的實際大小要比unitsize多sizeof(memunit_info_t)個位元組

  2. 空閒記憶體單元連結串列中的記憶體單元是亂序串聯的,因此會造成即使空閒記憶體單元個數超過單個Memblock_t記憶體單元總數時,作業系統可能依然無法釋放任何一個Memblock_t物件。