0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何實現(xiàn)一個高性能內(nèi)存池

科技綠洲 ? 來源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-10 11:11 ? 次閱讀

寫在前面

本文的內(nèi)存池代碼是改編自Nginx的內(nèi)存池源碼,思路幾乎一樣。由于Nginx源碼的變量命名我不喜歡,又沒有注釋,看得我很難受。想自己寫一版容易理解的代碼。

應(yīng)用場景

寫內(nèi)存池的原理之前,按照慣例先說內(nèi)存池的應(yīng)用場景。

為什么我們需要內(nèi)存池?

  1. 因為malloc等分配內(nèi)存的方式,需要涉及到系統(tǒng)調(diào)用sbrk,頻繁的malloc和free會消耗系統(tǒng)資源。

既然如此,我們就預(yù)先在用戶態(tài)創(chuàng)建一個緩存空間,作為內(nèi)存池。

每次malloc的時候,從用戶態(tài)的內(nèi)存池中獲得分配的內(nèi)存,不走系統(tǒng)調(diào)用,就能像賽車加氮氣一樣超級加速內(nèi)存管理。

圖片

2.如果頻繁地malloc和free,由于malloc的地址是不確定的,因為每次malloc的時候,會先在freelist中找一個適合其大小的塊,如果找不到,才會調(diào)用sbrk直接拓展堆的內(nèi)存邊界。(freelist是之前free掉的內(nèi)存,內(nèi)核會將其組織成一個鏈表,留待下次malloc的時候查找使用。)

因為不確定,所以容易產(chǎn)生內(nèi)存碎片。

圖片

如果我們需要4個字節(jié)的空間,卻因為malloc的位置隨機分配在這個滑稽的位置,就導(dǎo)致雖然我們有2+2的空間但是只能望洋興嘆的尷尬處境。

Nginx內(nèi)存池的特點

nginx中線程池與內(nèi)存池都是池,核心思想都是對系統(tǒng)的資源調(diào)度起一個緩沖的作用,但是多少還是有點區(qū)別。

對于線程池來說,兩隊列+中樞的架構(gòu),在不同公司的框架實現(xiàn)都大同小異。

但是內(nèi)存池卻是在不同的框架中,實現(xiàn)不盡相同。

為什么會有區(qū)別,根本原因是面向的實際業(yè)務(wù)不同,從而導(dǎo)致不同公司的內(nèi)存池會靈活變通,有各自鮮明的特點。

在Nginx的服務(wù)器中,每當(dāng)有一個客戶端connect進來后,就會為其單獨創(chuàng)建一個內(nèi)存池,用于recv和send的緩沖區(qū)buffer。

所以Nginx的內(nèi)存池的特點是,其包含了兩種內(nèi)存分配方式,大內(nèi)存和小內(nèi)存使用不同的數(shù)據(jù)結(jié)構(gòu)來存儲。從而適應(yīng)客戶端不同的請求,如果只是一些簡單的表單,就用小內(nèi)存,如果是上傳下載大文件,就用大內(nèi)存。

同時Nginx的內(nèi)存池還有一個重要的特點:不像線程池會回收利用所有線程,Nginx的內(nèi)存池不回收小內(nèi)存的buffer,只回收大內(nèi)存的buffer。

同樣是出于實際業(yè)務(wù)的考慮,每個內(nèi)存池都對應(yīng)一個客戶,那么一個客戶端產(chǎn)生的小內(nèi)存碎片自然不會太多,即使不回收,也不會有太大代價。

同時tcp本身就有keep-alive機制,超過一定時間就斷開,Nginx是典型的將不同客戶端分發(fā)到多進程的網(wǎng)絡(luò)模型,連接斷開,進程結(jié)束,從而對應(yīng)的內(nèi)存池會釋放,相當(dāng)于一次性回收所有的大小內(nèi)存。

但是在連接中大內(nèi)存因為占用空間大,Nginx覺得還是有必要回收,所以只做了回收大內(nèi)存這個接口。

數(shù)據(jù)結(jié)構(gòu)

我們先講核心的,小內(nèi)存的實現(xiàn)。

首先整個內(nèi)存池pool中有兩條鏈,一條是big block鏈,一條是small block鏈。

一個small block我們可以看做是一個小緩沖區(qū),而整條鏈的小緩沖區(qū)串起來組成一個大緩沖區(qū),就是內(nèi)存池了。

small block數(shù)據(jù)結(jié)構(gòu)如下:

class small_block{
public:
char * cur_usable_buffer;
char * buffer_end;
small_block * next_block;
int no_enough_times;
};
  • cur_usable_buffer:指向該block的可用buffer的首地址
  • buffer_end:指向該block的buffer的結(jié)尾地址
  • next_block: 指向block鏈的下一個small block
  • no_enough_times:每次分配內(nèi)存,都要順著small block鏈,找鏈中的每個小緩沖區(qū)看是否有足夠分配的內(nèi)存,如果在該block沒找到,就會將該值+1,代表沒有足夠空間命中的次數(shù)。

對于small block,我們看它的格局要更上層一點,達到看山不是山,看水不是水的境界,因為它不僅僅是單獨的block對象,還代表了后面跟著的buffer,當(dāng)鏈中所有小緩沖區(qū)都不夠位置分配新的空間時,就會創(chuàng)建新的small block,而創(chuàng)建的時候,會一次性創(chuàng)建small_block+buffer_capacity大小的空間。

為什么要這么設(shè)置,而不是先malloc一個small_block,再malloc一個small_buffer,別問,問就是不優(yōu)雅,像這樣分配內(nèi)存,相當(dāng)于兩個連在一起形成一個整塊,很舒適,適合強迫癥,而不是東一塊隨機地址,西一塊隨機地址,那還叫池嗎?干脆叫地下水管道吧,一堆支流。

我們不需要在small_block的數(shù)據(jù)結(jié)構(gòu)中存buffer的首地址指針,因為很自然的是,我們拿到small_block的指針后自然也就拿到了buffer的首地址指針

即 buffer_head_ptr = (char*)small_block + sizeof(small_block);

small_block中各指針的指向:

圖片

然后是整個內(nèi)存池pool的數(shù)據(jù)結(jié)構(gòu)

class memory_pool{
public:
size_t small_buffer_capacity;
small_block * cur_usable_small_block;
big_block * big_block_start;
small_block small_block_start[0];
//-----------------------------上面是成員,下面是api--------------------------------------------
static memory_pool * createPool(size_t capacity);
static void destroyPool(memory_pool * pool);
static char* createNewSmallBlock(memory_pool * pool,size_t size);
static char* mallocBigBlock(memory_pool * pool,size_t size);
static void* poolMalloc(memory_pool * pool,size_t size);
static void freeBigBlock(memory_pool * pool, char *buffer_ptr);
};
  • small_buffer_capacity:對于Nginx的內(nèi)存池,每個small buffer的大小都是一樣的,所以該值代表了small buffer的容量,在創(chuàng)建內(nèi)存池的時候作為參數(shù)確定。
  • cur_usable_small_block:每次要分配小內(nèi)存的時候,并不會從頭開始找合適的空間,而是從這個指針指向的small_block開始找。
  • big_block_start:big block鏈的鏈頭
  • small_block_start:small block的鏈頭。

這里要提一點的是,該類的最后一個成員small_block_start,其為一個長度為0的數(shù)組,這在C99中是一種柔性數(shù)組的寫法,所以比較吃編譯器,我是在linux環(huán)境下編譯,沒什么問題,但是vs環(huán)境可能會報錯,如果報錯就設(shè)置長度為1。

為什么要這么設(shè)置,后面講第一個api createPool 的時候會說。

還有全員使用靜態(tài)成員方法也是與此有關(guān),因為Nginx是純C寫的,我使用靜態(tài)成員方法也是一種兼容C的面向過程函數(shù)的變種吧(笑~)。

最后是big block的數(shù)據(jù)結(jié)構(gòu)

class big_block{
public:
char * big_buffer;
big_block * next_block;
};
  • big_buffer:大內(nèi)存buffer的首地址
  • next_block:因為big block也是鏈式結(jié)構(gòu),指向下一個big block

big block最簡單,因為Nginx似乎不在乎big block優(yōu)不優(yōu)雅了,其big_block和big_buffer的地址就是分開的,不會連在一起。

圖片

接口實現(xiàn)

創(chuàng)建內(nèi)存池 createPool:

這里需要再次提到剛剛的柔性數(shù)組,small_block_start

Nginx希望創(chuàng)建內(nèi)存池pool的時候,不是單獨一個孤零零的pool對象,而是創(chuàng)建pool的同時,就創(chuàng)建第一個small_block,而創(chuàng)建一個新的small_block又需要同步建立一個small_buffer,而Nginx希望這三個對象的內(nèi)存是連起來的,如圖所示,于是優(yōu)雅再次出現(xiàn)。

柔性數(shù)組的意義在于,我們希望在memory_pool保留一個指針錨點來指向第一個small_block而不是通過剛剛的指針加法來找到small_block的首地址,那便使用一個0長度的數(shù)組作為這個錨點。

這樣malloc整段內(nèi)存,small_block就會接在memory_pool的后面,且以small_block_start的形式成為pool的成員,實際上small_block_start長度為0是不占pool的內(nèi)存空間的。

而為什么使用靜態(tài)成員函數(shù)也是這個原因,使用柔性數(shù)組必須保證其位置定義在整個類的內(nèi)存空間的末尾,靜態(tài)函數(shù)雖然在類中聲明,但是實際會存放在靜態(tài)區(qū)中保存,不占用類的內(nèi)存。

//-創(chuàng)建內(nèi)存池并初始化,api以靜態(tài)成員(工廠)的方式模擬C風(fēng)格函數(shù)實現(xiàn)

//-capacity是buffer的容量,在初始化的時候確定,后續(xù)所有小塊的buffer都是這個大小

memory_pool * memory_pool::createPool(size_t capacity){
//-我們先分配一大段連續(xù)內(nèi)存,該內(nèi)存可以想象成這段內(nèi)存由pool+small_block+small_block_buffers三個部分組成.
//-為什么要把三個部分(可以理解為三個對象)用連續(xù)內(nèi)存來存,因為這樣整個池看起來比較優(yōu)雅.各部分地址不會天女散花地落在內(nèi)存的各個角落.
size_t total_size = sizeof(memory_pool)+sizeof(small_block)+capacity;
void *temp = malloc(total_size);
memset(temp,0,total_size);

memory_pool * pool = (memory_pool*)temp;
fprintf(stdout,"pool address:%pn",pool);
//-此時temp是pool的指針,先來初始化pool對象
pool ->small_buffer_capacity = capacity;
pool ->big_block_start = nullptr;
pool ->cur_usable_small_block = (small_block*)(pool->small_block_start);

//-pool+1的1是整個memory_pool的步長,別弄錯了。此時sbp是small_block的指針
small_block * sbp = (small_block*)(pool+1);
fprintf(stdout,"first small block address:%pn",sbp);
//-初始化small_block對象
sbp -> cur_usable_buffer = (char*)(sbp+1);
fprintf(stdout,"first small block buffer address:%pn",sbp->cur_usable_buffer);
sbp -> buffer_end = sbp->cur_usable_buffer+capacity;//-第一個可用的buffer就是開頭,所以end=開頭+capacity
sbp -> next_block = nullptr;
sbp -> no_enough_times = 0;

return pool;
};

代替malloc的分配內(nèi)存的接口:poolMalloc

第一步,我們判斷poolMalloc的size是一個大內(nèi)存還是小內(nèi)存。

如果是大內(nèi)存就走mallocBigBlock這個api。

如果是小內(nèi)存,就從cur_usable_small_block這個small block開始找足夠的空間去分配內(nèi)存,注意并不是從small block鏈的開頭開始尋找。因為大概率cur_usable_small_block之前的所有small block都已經(jīng)分配完了,所以為了提高命中效率,需要這樣一個指針指向?qū)ふ业拈_始。

圖片

對于每個small block,我們直接用buffer_end 和 cur_usable_buffer相減就可以得到一個small buffer的剩余容量去判斷是否能分配。

圖片

如果空間足夠,就從cur_usable_buffer開始分配size大小的空間,并返回這段空間的首地址,同時更新cur_usable_buffer指向新的剩余空間。

如果直到鏈的末尾都沒有足夠的size大小的空間,那就需要創(chuàng)建新的small block,走createNewSmallBlock這個api。

//-分配內(nèi)存
void* memory_pool::poolMalloc(memory_pool * pool,size_t size){
//-先判斷要malloc的是大內(nèi)存還是小內(nèi)存
if(sizesmall_buffer_capacity){//-如果是小內(nèi)存
//-從cur small block開始尋找
small_block * temp = pool -> cur_usable_small_block;
do{
//-判斷當(dāng)前small block的buffer夠不夠分配
//-如果夠分配,直接返回
if(temp->buffer_end-temp->cur_usable_buffer>size){
char * res = temp->cur_usable_buffer;
temp -> cur_usable_buffer = temp->cur_usable_buffer+size;
return res;
}
temp = temp->next_block;
}while (temp);
//-如果最后一個small block都不夠分配,則創(chuàng)建新的small block;
//-該small block在創(chuàng)建后,直接預(yù)先分配size大小的空間,所以返回即可.
return createNewSmallBlock(pool,size);
}
//-分配大內(nèi)存
return mallocBigBlock(pool,size);
}

創(chuàng)建新的小內(nèi)存塊:createNewSmallBlock

首先創(chuàng)建一個smallblock和連帶的buffer,還是如這張圖所示:

圖片

因為我們創(chuàng)建的目的是為了分配size空間,所以初始化后,便預(yù)留size大小的buffer,對cur_usable_buffer進行更新。

值得提的是,每次到了創(chuàng)建新的small block的環(huán)節(jié),就意味著目前鏈上的small buffer空間已經(jīng)都分配得差不多了,可能需要更新cur_usable_small_block,這就需要用到small block的no_enough_times成員,將cur_usable_small_block開始的每個small block的該值++,Nginx設(shè)置的經(jīng)驗值閾值是4,超過4,意味著該block不適合再成為尋找的開始了,需要往后繼續(xù)嘗試。

//-當(dāng)所有small block都沒有足夠空間分配,則創(chuàng)建新的small block并分配size空間,返回分配空間的首指針
char* memory_pool::createNewSmallBlock(memory_pool * pool,size_t size){
//-先創(chuàng)建新的small block,注意還有buffer
size_t malloc_size = sizeof(small_block)+pool->small_buffer_capacity;
void * temp = malloc(malloc_size);
memset(temp,0,malloc_size);

//-初始化新的small block
small_block * sbp = (small_block *)temp;
fprintf(stdout,"new small block address:%pn",sbp);
sbp -> cur_usable_buffer = (char*)(sbp+1);//-跨越一個small_block的步長
fprintf(stdout,"new small block buffer address:%pn",sbp->cur_usable_buffer);
sbp -> buffer_end = (char*)temp+malloc_size;
sbp -> next_block = nullptr;
sbp -> no_enough_times = 0;
//-預(yù)留size空間給新分配的內(nèi)存
char* res = sbp -> cur_usable_buffer;//-存?zhèn)€副本作為返回值
sbp -> cur_usable_buffer = res+size;

//-因為目前的所有small_block都沒有足夠的空間了。
//-意味著可能需要更新線程池的cur_usable_small_block,也就是尋找的起點
small_block * p = pool -> cur_usable_small_block;
while(p->next_block){
if(p->no_enough_times>4){
pool -> cur_usable_small_block = p->next_block;
}
++(p->no_enough_times);
p = p->next_block;
}

//-此時p正好指向當(dāng)前pool中最后一個small_block,將新節(jié)點接上去。
p->next_block = sbp;

//-因為最后一個block有可能no_enough_times>4導(dǎo)致cur_usable_small_block更新成nullptr
//-所以還要判斷一下
if(pool -> cur_usable_small_block == nullptr){
pool -> cur_usable_small_block = sbp;
}
return res;//-返回新分配內(nèi)存的首地址
}

分配大內(nèi)存空間:mallocBigBlock

如果size超過了預(yù)設(shè)的capacity,那就會走這個api。

其同樣也是一個鏈式查找的過程,只不過比查找small block更快更粗暴。

big block鏈沒有類似cur_usable_small_block這樣的節(jié)點,只要從頭開始遍歷,如果有空buffer就返回該block,如果超過3個還沒找到(同樣是Nginx的經(jīng)驗值)就直接不找了,創(chuàng)建新的big block。

圖片

還有一點值得注意的是,big_buffer是個大內(nèi)存,所以其是個malloc的隨機地址,

但是big_block本身是一個小內(nèi)存,那就不應(yīng)該還是用隨機地址,應(yīng)該保存在內(nèi)存池內(nèi)部的空間。

所以這里有個套娃的內(nèi)存池poolMalloc操作,用來分配big_block的空間。

//-分配大塊的內(nèi)存
char* memory_pool::mallocBigBlock(memory_pool * pool,size_t size){
//-先分配size大小的空間
void*temp = malloc(size);
memset(temp, 0, size);

//-從big_block_start開始尋找,注意big block是一個棧式鏈,插入新元素是插入到頭結(jié)點的位置。
big_block * bbp = pool->big_block_start;
int i = 0;
while(bbp){
if(bbp->big_buffer == nullptr){
bbp->big_buffer = (char*)temp;
return bbp->big_buffer;
}
if(i>3){
break;//-為了保證效率,如果找三輪還沒找到有空buffer的big_block,就直接建立新的big_block
}
bbp = bbp->next_block;
++i;
}

//-創(chuàng)建新的big_block,這里比較難懂的點,就是Nginx覺得big_block的buffer雖然是一個隨機地址的大內(nèi)存
//-但是big_block本身算一個小內(nèi)存,那就不應(yīng)該還是用隨機地址,應(yīng)該保存在內(nèi)存池內(nèi)部的空間。
//-所以這里有個套娃的內(nèi)存池malloc操作
big_block* new_bbp = (big_block*)memory_pool::poolMalloc(pool,sizeof(big_block));
//-初始化
new_bbp -> big_buffer = (char*)temp;
new_bbp ->next_block = pool->big_block_start;
pool -> big_block_start = new_bbp;

//-返回分配內(nèi)存的首地址
return new_bbp->big_buffer;
}

釋放大內(nèi)存:freeBigBlock:

由于big block是一個鏈式結(jié)構(gòu),所以要找到對應(yīng)的buffer并free掉,就需要從這個鏈的開頭開始遍歷,一直到找到位置。

//-釋放大內(nèi)存的buffer,由于是一個鏈表,所以,確實,這是效率最低的一個api了
void memory_pool::freeBigBlock(memory_pool * pool, char *buffer_ptr){
big_block* bbp = pool -> big_block_start;
while(bbp){
if(bbp->big_buffer == buffer_ptr){
free(bbp->big_buffer);
bbp->big_buffer = nullptr;
return;
}
bbp = bbp->next_block;
}
}

銷毀線程池:destroyPool:

這個思路也很簡單,pool中有兩條鏈分別指向大內(nèi)存和小內(nèi)存,那么分別沿著這兩條鏈去free掉內(nèi)存即可,由于大內(nèi)存的buffer和big block不是一起malloc的,所以只需要free掉buffer,而big block是分配在小內(nèi)存池中的,所以,之后free掉小內(nèi)存的時候會順帶一起free掉。

比較值得注意的一點是,small鏈的free不是從第一個small block開始的,而是第二個small block。如圖所示,第一個small block的空間是和pool一起malloc出來的,不需要free,只要最后的時候free pool就會一起釋放掉。

圖片

void memory_pool::destroyPool(memory_pool * pool){

//-銷毀大內(nèi)存

big_block * bbp = pool->big_block_start;
while(bbp){
if(bbp->big_buffer){
free(bbp->big_buffer);
bbp->big_buffer = nullptr;
}
bbp = bbp->next_block;
}
//-為什么不刪除big_block節(jié)點?因為big_block在小內(nèi)存池中,等會就和小內(nèi)存池一起銷毀了

//-銷毀小內(nèi)存
small_block * temp = pool -> small_block_start->next_block;
while(temp){
small_block * next = temp -> next_block;
free(temp);
temp = next;
}
free(pool);
}

測試代碼

測試一下用內(nèi)存池分配的地址是否如我們所設(shè)計的那樣。

int main(){
memory_pool * pool = memory_pool::createPool(1024);
//-分配小內(nèi)存
char*p1 = (char*)memory_pool::poolMalloc(pool,2);
fprintf(stdout,"little malloc1:%pn",p1);
char*p2 = (char*)memory_pool::poolMalloc(pool,4);
fprintf(stdout,"little malloc2:%pn",p2);
char*p3 = (char*)memory_pool::poolMalloc(pool,8);
fprintf(stdout,"little malloc3:%pn",p3);
char*p4 = (char*)memory_pool::poolMalloc(pool,256);
fprintf(stdout,"little malloc4:%pn",p4);
char*p5 = (char*)memory_pool::poolMalloc(pool,512);
fprintf(stdout,"little malloc5:%pn",p5);

//-測試分配不足開辟新的small block
char*p6 = (char*)memory_pool::poolMalloc(pool,512);
fprintf(stdout,"little malloc6:%pn",p6);

//-測試分配大內(nèi)存
char*p7 = (char*)memory_pool::poolMalloc(pool,2048);
fprintf(stdout,"big malloc1:%pn",p7);

char*p8 = (char*)memory_pool::poolMalloc(pool,4096);
fprintf(stdout,"big malloc2:%pn",p8);

//-測試free大內(nèi)存
memory_pool::freeBigBlock(pool, p8);

//-測試再次分配大內(nèi)存(我這里測試結(jié)果和p8一樣)
char*p9 = (char*)memory_pool::poolMalloc(pool,2048);
fprintf(stdout,"big malloc3:%pn",p9);

//-銷毀內(nèi)存池
memory_pool::destroyPool(pool);

exit(EXIT_SUCCESS);
}

完整代碼

// * 改編自nginx內(nèi)程池,由于nginx源碼純C,我這里也用C接口進行內(nèi)存管理。
// * 修改了很多nginx中晦澀的變量名,比較容易理解
#include
#include
#include
using namespace std;

class small_block{
public:
char * cur_usable_buffer;
char * buffer_end;
small_block * next_block;
int no_enough_times;
};

class big_block{
public:
char * big_buffer;
big_block * next_block;
};

class memory_pool{
public:
size_t small_buffer_capacity;
small_block * cur_usable_small_block;
big_block * big_block_start;
small_block small_block_start[0];
static memory_pool * createPool(size_t capacity);
static void destroyPool(memory_pool * pool);
static char* createNewSmallBlock(memory_pool * pool,size_t size);
static char* mallocBigBlock(memory_pool * pool,size_t size);
static void* poolMalloc(memory_pool * pool,size_t size);
static void freeBigBlock(memory_pool * pool, char *buffer_ptr);
};

//-創(chuàng)建內(nèi)存池并初始化,api以靜態(tài)成員(工廠)的方式模擬C風(fēng)格函數(shù)實現(xiàn)
//-capacity是buffer的容量,在初始化的時候確定,后續(xù)所有小塊的buffer都是這個大小
memory_pool * memory_pool::createPool(size_t capacity){
//-我們先分配一大段連續(xù)內(nèi)存,該內(nèi)存可以想象成這段內(nèi)存由pool+small_block+small_block_buffers三個部分組成.
//-為什么要把三個部分(可以理解為三個對象)用連續(xù)內(nèi)存來存,因為這樣整個池看起來比較優(yōu)雅.各部分地址不會天女散花地落在內(nèi)存的各個角落.
size_t total_size = sizeof(memory_pool)+sizeof(small_block)+capacity;
void *temp = malloc(total_size);
memset(temp,0,total_size);

memory_pool * pool = (memory_pool*)temp;
fprintf(stdout,"pool address:%pn",pool);
//-此時temp是pool的指針,先來初始化pool對象
pool ->small_buffer_capacity = capacity;
pool ->big_block_start = nullptr;
pool ->cur_usable_small_block = (small_block*)(pool->small_block_start);

//-pool+1的1是整個memory_pool的步長,別弄錯了。此時sbp是small_block的指針
small_block * sbp = (small_block*)(pool+1);
fprintf(stdout,"first small block address:%pn",sbp);
//-初始化small_block對象
sbp -> cur_usable_buffer = (char*)(sbp+1);
fprintf(stdout,"first small block buffer address:%pn",sbp->cur_usable_buffer);
sbp -> buffer_end = sbp->cur_usable_buffer+capacity;//-第一個可用的buffer就是開頭,所以end=開頭+capacity
sbp -> next_block = nullptr;
sbp -> no_enough_times = 0;

return pool;
};

//-銷毀內(nèi)存池
void memory_pool::destroyPool(memory_pool * pool){
//-銷毀大內(nèi)存
big_block * bbp = pool->big_block_start;
while(bbp){
if(bbp->big_buffer){
free(bbp->big_buffer);
bbp->big_buffer = nullptr;
}
bbp = bbp->next_block;
}
//-為什么不刪除big_block節(jié)點?因為big_block在小內(nèi)存池中,等會就和小內(nèi)存池一起銷毀了

//-銷毀小內(nèi)存
small_block * temp = pool -> small_block_start->next_block;
while(temp){
small_block * next = temp -> next_block;
free(temp);
temp = next;
}
free(pool);
}

//-當(dāng)所有small block都沒有足夠空間分配,則創(chuàng)建新的small block并分配size空間,返回分配空間的首指針
char* memory_pool::createNewSmallBlock(memory_pool * pool,size_t size){
//-先創(chuàng)建新的small block,注意還有buffer
size_t malloc_size = sizeof(small_block)+pool->small_buffer_capacity;
void * temp = malloc(malloc_size);
memset(temp,0,malloc_size);

//-初始化新的small block
small_block * sbp = (small_block *)temp;
fprintf(stdout,"new small block address:%pn",sbp);
sbp -> cur_usable_buffer = (char*)(sbp+1);//-跨越一個small_block的步長
fprintf(stdout,"new small block buffer address:%pn",sbp->cur_usable_buffer);
sbp -> buffer_end = (char*)temp+malloc_size;
sbp -> next_block = nullptr;
sbp -> no_enough_times = 0;
//-預(yù)留size空間給新分配的內(nèi)存
char* res = sbp -> cur_usable_buffer;//-存?zhèn)€副本作為返回值
sbp -> cur_usable_buffer = res+size;

//-因為目前的所有small_block都沒有足夠的空間了。
//-意味著可能需要更新線程池的cur_usable_small_block,也就是尋找的起點
small_block * p = pool -> cur_usable_small_block;
while(p->next_block){
if(p->no_enough_times>4){
pool -> cur_usable_small_block = p->next_block;
}
++(p->no_enough_times);
p = p->next_block;
}

//-此時p正好指向當(dāng)前pool中最后一個small_block,將新節(jié)點接上去。
p->next_block = sbp;

//-因為最后一個block有可能no_enough_times>4導(dǎo)致cur_usable_small_block更新成nullptr
//-所以還要判斷一下
if(pool -> cur_usable_small_block == nullptr){
pool -> cur_usable_small_block = sbp;
}
return res;//-返回新分配內(nèi)存的首地址
}


//-分配大塊的內(nèi)存
char* memory_pool::mallocBigBlock(memory_pool * pool,size_t size){
//-先分配size大小的空間
void*temp = malloc(size);
memset(temp, 0, size);

//-從big_block_start開始尋找,注意big block是一個棧式鏈,插入新元素是插入到頭結(jié)點的位置。
big_block * bbp = pool->big_block_start;
int i = 0;
while(bbp){
if(bbp->big_buffer == nullptr){
bbp->big_buffer = (char*)temp;
return bbp->big_buffer;
}
if(i>3){
break;//-為了保證效率,如果找三輪還沒找到有空buffer的big_block,就直接建立新的big_block
}
bbp = bbp->next_block;
++i;
}

//-創(chuàng)建新的big_block,這里比較難懂的點,就是Nginx覺得big_block的buffer雖然是一個隨機地址的大內(nèi)存
//-但是big_block本身算一個小內(nèi)存,那就不應(yīng)該還是用隨機地址,應(yīng)該保存在內(nèi)存池內(nèi)部的空間。
//-所以這里有個套娃的內(nèi)存池malloc操作
big_block* new_bbp = (big_block*)memory_pool::poolMalloc(pool,sizeof(big_block));
//-初始化
new_bbp -> big_buffer = (char*)temp;
new_bbp ->next_block = pool->big_block_start;
pool -> big_block_start = new_bbp;

//-返回分配內(nèi)存的首地址
return new_bbp->big_buffer;
}

//-分配內(nèi)存
void* memory_pool::poolMalloc(memory_pool * pool,size_t size){
//-先判斷要malloc的是大內(nèi)存還是小內(nèi)存
if(sizesmall_buffer_capacity){//-如果是小內(nèi)存
//-從cur small block開始尋找
small_block * temp = pool -> cur_usable_small_block;
do{
//-判斷當(dāng)前small block的buffer夠不夠分配
//-如果夠分配,直接返回
if(temp->buffer_end-temp->cur_usable_buffer>size){
char * res = temp->cur_usable_buffer;
temp -> cur_usable_buffer = temp->cur_usable_buffer+size;
return res;
}
temp = temp->next_block;
}while (temp);
//-如果最后一個small block都不夠分配,則創(chuàng)建新的small block;
//-該small block在創(chuàng)建后,直接預(yù)先分配size大小的空間,所以返回即可.
return createNewSmallBlock(pool,size);
}
//-分配大內(nèi)存
return mallocBigBlock(pool,size);
}

//-釋放大內(nèi)存的buffer,由于是一個鏈表,所以,確實,這是效率最低的一個api了
void memory_pool::freeBigBlock(memory_pool * pool, char *buffer_ptr){
big_block* bbp = pool -> big_block_start;
while(bbp){
if(bbp->big_buffer == buffer_ptr){
free(bbp->big_buffer);
bbp->big_buffer = nullptr;
return;
}
bbp = bbp->next_block;
}
}

int main(){
memory_pool * pool = memory_pool::createPool(1024);
//-分配小內(nèi)存
char*p1 = (char*)memory_pool::poolMalloc(pool,2);
fprintf(stdout,"little malloc1:%pn",p1);
char*p2 = (char*)memory_pool::poolMalloc(pool,4);
fprintf(stdout,"little malloc2:%pn",p2);
char*p3 = (char*)memory_pool::poolMalloc(pool,8);
fprintf(stdout,"little malloc3:%pn",p3);
char*p4 = (char*)memory_pool::poolMalloc(pool,256);
fprintf(stdout,"little malloc4:%pn",p4);
char*p5 = (char*)memory_pool::poolMalloc(pool,512);
fprintf(stdout,"little malloc5:%pn",p5);

//-測試分配不足開辟新的small block
char*p6 = (char*)memory_pool::poolMalloc(pool,512);
fprintf(stdout,"little malloc6:%pn",p6);

//-測試分配大內(nèi)存
char*p7 = (char*)memory_pool::poolMalloc(pool,2048);
fprintf(stdout,"big malloc1:%pn",p7);

char*p8 = (char*)memory_pool::poolMalloc(pool,4096);
fprintf(stdout,"big malloc2:%pn",p8);

//-測試free大內(nèi)存
memory_pool::freeBigBlock(pool, p8);

//-測試再次分配大內(nèi)存(我這里測試結(jié)果和p8一樣)
char*p9 = (char*)memory_pool::poolMalloc(pool,2048);
fprintf(stdout,"big malloc3:%pn",p9);

//-銷毀內(nèi)存池
memory_pool::destroyPool(pool);

exit(EXIT_SUCCESS);
}
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    2903

    瀏覽量

    73537
  • 緩存
    +關(guān)注

    關(guān)注

    1

    文章

    223

    瀏覽量

    26579
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    626

    瀏覽量

    28968
  • nginx
    +關(guān)注

    關(guān)注

    0

    文章

    139

    瀏覽量

    12115
收藏 人收藏

    評論

    相關(guān)推薦

    詳解內(nèi)存技術(shù)的原理與實現(xiàn)

    最近在網(wǎng)上看到了幾篇篇講述內(nèi)存技術(shù)的文章,有篇是有IBM中國研發(fā)中心的人寫的,寫的不錯~~文章地址在本篇blog最后。原文的講述比我的要清晰很多,我在這只是把我的些理解和遇到的
    的頭像 發(fā)表于 05-20 08:58 ?4830次閱讀
    詳解<b class='flag-5'>內(nèi)存</b><b class='flag-5'>池</b>技術(shù)的原理與<b class='flag-5'>實現(xiàn)</b>

    C++內(nèi)存的設(shè)計與實現(xiàn)

    內(nèi)存化技術(shù)中的種形式。通常我們在編寫程序的時候回使用 new delete 這些關(guān)鍵字來向操作系統(tǒng)申請內(nèi)存,而這樣造成的后果就是每次
    發(fā)表于 09-23 10:22 ?875次閱讀

    采用GDDR6的高性能內(nèi)存解決方案

    隨著網(wǎng)絡(luò)和數(shù)據(jù)中心帶寬需求的日益提升,針對高性能內(nèi)存解決方案的需求也是水漲船高。對于超過 400 Gbps 的系統(tǒng)開發(fā),以經(jīng)濟高效的方式實現(xiàn)內(nèi)存方案的
    發(fā)表于 12-03 07:14

    內(nèi)存可以調(diào)節(jié)內(nèi)存的大小嗎

    嵌入式–內(nèi)存直接上代碼,自己體會。嵌入式設(shè)備,般keil提供的堆很小,般都不使用。使用內(nèi)存
    發(fā)表于 12-17 07:00

    內(nèi)存的概念和實現(xiàn)原理概述

    { //內(nèi)存的概念和實現(xiàn)原理概述//malloc:內(nèi)存浪費,頻繁分配小塊內(nèi)存,則浪費更加顯
    發(fā)表于 12-17 06:44

    關(guān)于RT-Thread內(nèi)存管理的內(nèi)存簡析

    連接起來。分配內(nèi)存塊。在用戶申請內(nèi)存塊時,從空閑鏈表中取出第一個內(nèi)存塊給申請者。內(nèi)存工作機制如
    發(fā)表于 04-06 17:02

    RT-Thread內(nèi)存管理之內(nèi)存實現(xiàn)分析

    塊2找到內(nèi)存塊3,以此類推。到此也就完成了內(nèi)存的創(chuàng)建, 可以愉快的進行下步了。分配和釋放內(nèi)存塊從指定的
    發(fā)表于 10-17 15:06

    Linux下高性能定時器實現(xiàn)

    提出Linux用戶空間下的高性能定時器實現(xiàn)方法。主要基于時間輪、紅黑樹及Linux內(nèi)核提供了種利于管理的定時器句柄Timerfd。
    發(fā)表于 09-25 14:57 ?25次下載

    突破性能瓶頸,實現(xiàn)CPU與內(nèi)存高性能互連

    提供高性能、高安全、高可靠性的芯片產(chǎn)品,正是瀾起科技為之孜孜以求、奮斗多年的事業(yè)。 CPU和內(nèi)存是計算設(shè)備中缺不可的關(guān)鍵組件。近年來,隨著以云計算、大數(shù)據(jù)、移動互聯(lián)網(wǎng)、人工智能為代表的現(xiàn)代信息技術(shù)
    發(fā)表于 12-01 15:13 ?583次閱讀

    什么是內(nèi)存

    1什么是內(nèi)存 1.1化技術(shù) 所謂“化技術(shù)”,就是程序先向系統(tǒng)申請過量的資源,然后自己管理,以備不時之需。之所以要申請過 量的資源,是因為每次申請該資源都有較大的開銷,不如提前申請
    的頭像 發(fā)表于 11-08 16:26 ?693次閱讀
    什么是<b class='flag-5'>內(nèi)存</b><b class='flag-5'>池</b>

    高并發(fā)內(nèi)存項目實現(xiàn)

    本項目實現(xiàn)高并發(fā)內(nèi)存,參考了Google的開源項目tcmalloc實現(xiàn)的簡易版;其功能就
    的頭像 發(fā)表于 11-09 11:16 ?566次閱讀
    高并發(fā)<b class='flag-5'>內(nèi)存</b><b class='flag-5'>池</b>項目<b class='flag-5'>實現(xiàn)</b>

    了解連接、線程內(nèi)存、異步請求

    可被重復(fù)使用像常見的線程、內(nèi)存、連接、對象都具有以上的共同特點。 連接 什么是數(shù)據(jù)庫連
    的頭像 發(fā)表于 11-09 14:44 ?869次閱讀
    了解連接<b class='flag-5'>池</b>、線程<b class='flag-5'>池</b>、<b class='flag-5'>內(nèi)存</b><b class='flag-5'>池</b>、異步請求<b class='flag-5'>池</b>

    內(nèi)存的使用場景

    山中,非常容易出現(xiàn)內(nèi)存泄漏導(dǎo)致mmo的問題。 為了解決這兩問題,內(nèi)存就應(yīng)運而生了。內(nèi)存預(yù)先
    的頭像 發(fā)表于 11-10 17:19 ?563次閱讀
    <b class='flag-5'>內(nèi)存</b><b class='flag-5'>池</b>的使用場景

    nginx內(nèi)存源碼設(shè)計

    造輪子內(nèi)存原因引入 作為C/C++程序員, 相較JAVA程序員的重大特征是我們可以直接訪問內(nèi)存, 自己管理
    的頭像 發(fā)表于 11-13 11:51 ?608次閱讀
    nginx<b class='flag-5'>內(nèi)存</b><b class='flag-5'>池</b>源碼設(shè)計

    內(nèi)存主要解決的問題

    內(nèi)存的定義 1.化技術(shù) 是在計算機技術(shù)中經(jīng)常使用的種設(shè)計模式,其內(nèi)涵在于:將程序中需要經(jīng)常使用的核心資源 先申請出來,放到
    的頭像 發(fā)表于 11-13 15:23 ?571次閱讀
    <b class='flag-5'>內(nèi)存</b><b class='flag-5'>池</b>主要解決的問題