一文帶你玩轉(zhuǎn)C++內(nèi)存池的簡單原理及實現(xiàn)(純代碼解析)

一,為什么要用內(nèi)存池
C++程序默認的內(nèi)存管理(new,delete,malloc,free)會頻繁地在堆上分配和釋放內(nèi)存,導(dǎo)致性能的損失,產(chǎn)生大量的內(nèi)存碎片,降低內(nèi)存的利用率。默認的內(nèi)存管理因為被設(shè)計的比較通用,所以在性能上并不能做到極致。
因此,很多時候需要根據(jù)業(yè)務(wù)需求設(shè)計專用內(nèi)存管理器,便于針對特定數(shù)據(jù)結(jié)構(gòu)和使用場合的內(nèi)存管理,比如:內(nèi)存池。
二,內(nèi)存池原理
內(nèi)存池的思想是,在真正使用內(nèi)存之前,預(yù)先申請分配一定數(shù)量、大小預(yù)設(shè)的內(nèi)存塊留作備用。當有新的內(nèi)存需求時,就從內(nèi)存池中分出一部分內(nèi)存塊,若內(nèi)存塊不夠再繼續(xù)申請新的內(nèi)存,當內(nèi)存釋放后就回歸到內(nèi)存塊留作后續(xù)的復(fù)用,使得內(nèi)存使用效率得到提升,一般也不會產(chǎn)生不可控制的內(nèi)存碎片。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!??!前100名進群領(lǐng)取,額外贈送一份價值699的內(nèi)核資料包(含視頻教程、電子書、實戰(zhàn)項目及代碼)? ? ?


?
三,內(nèi)存池設(shè)計
算法原理:
預(yù)申請一個內(nèi)存區(qū)chunk,將內(nèi)存中按照對象大小劃分成多個內(nèi)存塊block
維持一個空閑內(nèi)存塊鏈表,通過指針相連,標記頭指針為第一個空閑塊
每次新申請一個對象的空間,則將該內(nèi)存塊從空閑鏈表中去除,更新空閑鏈表頭指針
每次釋放一個對象的空間,則重新將該內(nèi)存塊加到空閑鏈表頭
如果一個內(nèi)存區(qū)占滿了,則新開辟一個內(nèi)存區(qū),維持一個內(nèi)存區(qū)的鏈表,同指針相連,頭指針指向最新的內(nèi)存區(qū),新的內(nèi)存塊從該區(qū)內(nèi)重新劃分和申請
如圖所示:



內(nèi)存池實現(xiàn)
memory_pool.hpp
#ifndef _MEMORY_POOL_H_
#define _MEMORY_POOL_H_
#include <stdint.h>
#include <mutex>
template<size_t BlockSize, size_t BlockNum = 10>
class MemoryPool
{
public:
MemoryPool()
{
std::lock_guard<std::mutex> lk(mtx); // avoid race condition
// init empty memory pointer
free_block_head = NULL;
mem_chunk_head = NULL;
}
~MemoryPool()
{
std::lock_guard<std::mutex> lk(mtx); // avoid race condition
// destruct automatically
MemChunk* p;
while (mem_chunk_head)
{
p = mem_chunk_head->next;
delete mem_chunk_head;
mem_chunk_head = p;
}
}
void* allocate()
{
std::lock_guard<std::mutex> lk(mtx); // avoid race condition
// allocate one object memory
// if no free block in current chunk, should create new chunk
if (!free_block_head)
{
// malloc mem chunk
MemChunk* new_chunk = new MemChunk;
new_chunk->next = NULL;
// set this chunk's first block as free block head
free_block_head = &(new_chunk->blocks[0]);
// link the new chunk's all blocks
for (int i = 1; i < BlockNum; i++)
new_chunk->blocks[i - 1].next = &(new_chunk->blocks[i]);
new_chunk->blocks[BlockNum - 1].next = NULL; // final block next is NULL
if (!mem_chunk_head)
mem_chunk_head = new_chunk;
else
{
// add new chunk to chunk list
mem_chunk_head->next = new_chunk;
mem_chunk_head = new_chunk;
}
}
// allocate the current free block to the object
void* object_block = free_block_head;
free_block_head = free_block_head->next;
return object_block;
}
void* allocate(size_t size)
{
std::lock_guard<std::mutex> lk(array_mtx); // avoid race condition for continuous memory
// calculate objects num
int n = size / BlockSize;
// allocate n objects in continuous memory
// FIXME: make sure n > 0
void* p = allocate();
for (int i = 1; i < n; i++)
allocate();
return p;
}
void deallocate(void* p)
{
std::lock_guard<std::mutex> lk(mtx); // avoid race condition
// free object memory
FreeBlock* block = static_cast<FreeBlock*>(p);
block->next = free_block_head; // insert the free block to head
free_block_head = block;
}
private:
// free node block, every block size exactly can contain one object
struct FreeBlock
{
unsigned char data[BlockSize];
FreeBlock* next;
};
FreeBlock* free_block_head;
// memory chunk, every chunk contains blocks number with fixed BlockNum
struct MemChunk
{
FreeBlock blocks[BlockNum];
MemChunk* next;
};
MemChunk* mem_chunk_head;
// thread safe related
std::mutex mtx;
std::mutex array_mtx;
};
#endif // !_MEMORY_POOL_H_
運行結(jié)果:
p1 00000174BEDE0440 1
p2 00000174BEDE0450 2
p3 00000174BEDE0450 3
p4 00000174BEDE0460 4
p5 00000174BEDD5310 5
p6 00000174BEDD5320 6
可以看到內(nèi)存地址是連續(xù),并且回收一個節(jié)點后,依然有序地開辟內(nèi)存
對象先開辟內(nèi)存再構(gòu)造,先析構(gòu)再釋放內(nèi)存
注意
在內(nèi)存分配和釋放的環(huán)節(jié)需要加鎖來保證線程安全
還沒有實現(xiàn)對象數(shù)組的分配和釋放