最簡單的記憶體池 原理與實現

2021-08-30 20:13:17 字數 3099 閱讀 3404

記憶體池的主要作用,簡單地說來,便是提高記憶體的使用效率。堆記憶體的申請與釋放(new/delete及malloc/free),涉及複雜的記憶體分配演算法, 相比由簡單cpu指令支援的棧記憶體的申請與釋放,則是慢上了數量級。另一方面,棧的大小是有限制的,在需要大量記憶體的操作時,堆的使用是必要的。當然,頻繁地申請與釋放堆記憶體,效率是很低的,這也就是記憶體池出現的原因。

類似std::vector容器,在向vector裡新增乙個元素之前,該容器往往提前申請了一大塊記憶體,實際新增時就只需要拿出其中一小塊來使用,不用每新增乙個都使用new一次。記憶體池所做的,也就是一次申請一塊大記憶體,然後再小塊小塊地拿出來用:一次申請nm大小的記憶體,必然比n次申請m大小的記憶體要快,這就是記憶體池高效的簡單解釋。

一般來說,記憶體池可以按兩種維度劃分:單執行緒與多執行緒,可變大小和固定大小。其中最簡單的單執行緒固定大小的記憶體池。這類記憶體池只考慮單執行緒的程式,分配與**的物件都是固定大小的。這個簡單的記憶體池類模板宣告如下:

templateclass ******mempool

; struct memblock

;#pragma pack(pop)

******mempool(const ******mempool&) ;

memblock * head;

objectchunk * freechunk;

public:

******mempool();

~******mempool();

t* new();

void delete(t* t);

};

該記憶體池類模板例項化之後,產生乙個只用於申請物件t的記憶體池。建立物件的方法是t* new(),刪除物件的方法是void delete(t *)。類模板中還定義了兩個資料結構及兩個成員變數,還禁止了記憶體池的拷貝構造。對於這種記憶體池,它有兩層的鍊錶結構:第一層是記憶體塊鍊錶(memblock),由成員head為頭,把記憶體池中申請的大塊記憶體串接起來。memblock結構的next指標指向了下乙個記憶體塊,chunks則 為basesize個objectchunk物件。該鍊錶的每個結點都是記憶體池進行一次申請得到的大記憶體塊;第二層為自由記憶體塊鍊錶 (objectchunk),由freechunk為頭指標串著,該鍊錶的每乙個結點,都是乙個可以使用的小記憶體塊。大體結構如下圖(basesize為 5)所示:

當使用者向記憶體池申請乙個物件t時,記憶體池會檢查freechunk是否為null。不為null時,表示還有自由的小記憶體塊(objectchunk)可供使用,將它返回給使用者,並將freechunk移到下乙個自由記憶體塊;若freechunk為null,則記憶體池需要使用 new/malloc從申請一塊大記憶體(memblock),然後將其中的basesize個objectchunk都串連上形成鍊錶,再將 freechunk做為頭指標。這時freechunk不為null了,可以提供給使用者一塊小記憶體了(具體即是,取出鍊錶的首結點)。

template<>

class ******mempool

head->chunks[basesize-1].next = 0;

freechunk = & (head->chunks[0]);

}objectchunk * t = freechunk;

freechunk = freechunk->next;

return reinterpret_cast(t);}};

當使用者不再使用某塊小記憶體時,需要呼叫delete方法。此時,記憶體池把使用者不用的小塊記憶體,重新連線到以freechunk為首指標的鍊錶中就行了(將結點插入鍊錶的首位置)。**如下:

template<>

class ******mempool

};

當記憶體池不在使用時,析構釋放全部記憶體,此時只需要釋放head為首指標的鍊錶的每個結點(遍歷刪除)。**如下:

template<>

class ******mempool()}}

實現中,需要注意的是objectchunk的雙重身份:當它是自由記憶體塊,還未分配給使用者時,它是objectchunk的資料結構,包含乙個鏈 表指標,用於串連;當它分配給使用者時,它退出了自由記憶體塊鍊錶,它被強制轉換為t型別(**中用了reinterpret_cast操作符號強制轉換), 此時便不再有objectchunk裡的資料,不再需要鍊錶指標。所以objectchunk結構的大小,應該大於或等於t型別的大小(出現大於情況,是 因為objectchunk最小也必須包含乙個指標大小,而t型別卻小於乙個指標大小)。#pragma pack指令的使用,便是盡量使objectchunk的大小符合我們的預期(memblock也一樣)當使用者不再使用t型別物件時,便呼叫了 delete(t*),此時,t型別裡的資料內容都不再重要了,強制變為objectchunk後,把其內容覆蓋,使用前幾個位元組作為指標,又加入自由內 存煉表中。

總體說來,這個簡單的記憶體池,僅僅實現了一次(向系統)申請,多次分配(給使用者)的功能。對於大記憶體塊(memblock),採取的方法是只申不 放,僅在記憶體池銷毀時才一次性全部釋放。這樣的策略僅僅適用於記憶體申請與釋放頻繁,且記憶體充足的情況。而多執行緒,可變大小的情況,也需要更多考慮。

附完整**:

templateclass ******mempool

; struct memblock

;#pragma pack(pop)

******mempool(const ******mempool&)

memblock * head;

objectchunk * freechunk;

public:

******mempool() : head(0), freechunk(0)

~******mempool()

}t* new()

head->chunks[basesize-1].next = 0;

freechunk = & (head->chunks[0]);

}objectchunk * t = freechunk;

freechunk = freechunk->next;

return reinterpret_cast(t);

}void delete(t* t)

};

記憶體池 簡單的記憶體池的實現

當頻繁地用malloc申請記憶體,然後再用free釋放記憶體時,會存在兩個主要問題。第乙個問題是頻繁的分配釋放記憶體可能導致系統記憶體碎片過多 第二個問題是分配釋放記憶體花費的時間可能比較多 這個問題不太明顯 這個時候我們就可以考慮使用記憶體池了。最樸素的記憶體池思想就是,首先你向系統申請一塊很大的...

C 記憶體池的簡單原理及實現

c 程式預設的記憶體管理 new,delete,malloc,free 會頻繁地在堆上分配和釋放記憶體,導致效能的損失,產生大量的記憶體碎片,降低記憶體的利用率。預設的記憶體管理因為被設計的比較通用,所以在效能上並不能做到極致。因此,很多時候需要根據業務需求設計專用記憶體管理器,便於針對特定資料結構...

簡單的記憶體池實現

記憶體池是用來改善new delete記憶體管理機制可能造成的執行效率低下問題的一種技術 經典的記憶體池技術,是用一種用於分配大量大小相同的小物件技術,加快記憶體分配或者釋放的過程。在看書的過程中,我對簡單的實現乙個記憶體池的實現理解是 1 讓我們在堆上面申請到的記憶體是連續的。2 我們放在記憶體池...