高效記憶體池的設計方案[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物件。