山寨STL實現之記憶體池

2022-04-28 06:40:41 字數 3713 閱讀 2891

記憶體池的作用:

減少記憶體碎片,提高效能。

首先不得不提的是win32和x64中對於指標的長度是不同的,在win32中乙個指標佔4位元組,而在x64中乙個指標佔8位元組。也正是不清楚這一點,當我在x64中將指標作為4位元組修改造成其他資料異常。

首先我們先來定義三個巨集

#define align     sizeof(void*)

#define max_bytes 128

#define max_count (max_bytes / align)

正如前面所說的,為了相容win32與x64應此我們將要申請的記憶體按void*的大小來對齊。正如前面所說的,我們認為小於128位元組的記憶體為小記憶體,會產生記憶體碎片,應此在申請時應該勁量申請一塊較大的記憶體而將其中的一小塊分配給他。

然後讓我們來看一下記憶體池中的成員變數

struct

obj ;

struct

block

;obj*chunk_list[max_count];

size_t chunk_count;

block* free_list;

這裡使用obj結構來儲存已釋放記憶體的列表,這樣做的好處是可以更節省記憶體。在win32中使用這塊記憶體的前4位元組來指向下乙個節點,而在x64中使用這塊記憶體的前8位元組來指向下乙個節點。

chunk_list:儲存通過deallocate或refill中釋放或是新申請的記憶體塊列表,deallocate和refill將會在下文中介紹。

chunk_count:記憶體塊列表中已有的記憶體塊數量。

free_list:儲存了通過malloc申請記憶體塊的鍊錶。

下面我們來看一下記憶體池的建構函式與析構函式

memorypool() : free_list(0), chunk_count(0

)

~memorypool()

}

建構函式中初始化free_list和chunk_count為0,並初始化chunk_list為乙個空列表。而在析構函式中我們必須釋放每一塊通過malloc申請的大記憶體塊。

接下來是記憶體的申請

template t* allocate(size_t n, void(*h)(size_t))

return

p; }

const

int i =index(round_up(n));

obj* p =chunk_list[i];

if(p == 0

)

chunk_list[i] = p->next;

return reinterpret_cast(p);

}

值得注意的是,在呼叫時必須傳入乙個函式指標作為引數,當malloc申請記憶體失敗時會去呼叫這個函式來釋放出足夠多的記憶體空間。當要申請的記憶體大小超過128位元組時,呼叫預設的malloc為其申請記憶體。否則先查詢列表中是否還有足夠的空間分配給它,若已沒有足夠的空間分配給它,則呼叫refill申請一塊大記憶體。

然後是記憶體釋放函式deallocate

template void deallocate(t*p, size_t n)

const

int i =index(round_up(n));

reinterpret_cast

(p)->next =chunk_list[i];

chunk_list[i] = reinterpret_cast(p);

}

值得注意的是在釋放時必須給出這塊記憶體塊的大小。若這塊記憶體大於128位元組時,呼叫預設的free函式釋放掉這塊記憶體。否則將其加到對應的chunk_list列表內。

然後是調整記憶體塊大小函式reallocate

template t* reallocate(t* p, size_t old_size, size_t new_size, void(*h)(size_t))

if(round_up(old_size) == round_up(new_size)) return

p; t* result = allocate(new_size, h);

const size_t copy_size = new_size > old_size ?old_size : new_size;

memcpy(result, p, copy_size);

deallocate

(p, old_size);

return

result;

}

引數中必須給出這塊記憶體的原始大小和要調整後的大小,同時也必須給出當記憶體不足時的釋放函式的指標。若舊記憶體塊和新記憶體塊的大小都大於128位元組時,呼叫預設的realloc函式重新分配記憶體。否則先按調整後的大小申請一塊記憶體,並把原來的內容拷貝過來,最後釋放掉原來的記憶體塊。這裡並不建議使用這個函式,而是手動的去重新申請記憶體並拷貝釋放。

然後來看4個非常簡單的計算函式

inline size_t round_up(size_t bytes)const

inline size_t round_down(size_t bytes)

const

inline

int index(size_t bytes)const

inline size_t obj_count(

int i)const

return

result;

}

前3個用於記憶體對齊和計算索引,最後乙個用於獲取一在空閒列表內乙個記憶體塊的數量。

然後是refill函式,用於在沒有空閒記憶體塊時申請一塊大記憶體塊

template t* refill(int i, void(*h)(size_t))

block* pblock = (block*)malloc(sizeof

(block));

while(pblock == 0

)

pblock->data =p;

pblock->next =free_list;

free_list =pblock;

obj* current = (obj*)(p +presize);

for(int j = 0; j < count - 1; ++j)

chunk_count += count - 1

; rebalance();

return reinterpret_cast(p);

}

首先申請乙個大記憶體塊,然後將這塊申請到的記憶體塊放入free_list鍊錶內,最後組織起chunk_list中對應記憶體卡塊的鍊錶,然後重新調整chunk_list列表,最後將申請到的記憶體塊返回。

最後來看一下調整函式rebalance

void

rebalance()

else

++chunk_count;}}

}}

這裡從後至前檢視對應記憶體塊空閒鍊錶的長度,若超過平均數量,則將其切分為2塊較小的記憶體塊放入對應的鍊錶內。這樣做的好處是可以形成乙個金字塔形的分布狀況,既越小的記憶體塊大小擁有的節點數量越多,正如本文開頭所說,使用記憶體池是為了解決在申請小塊記憶體時造成的記憶體碎片。

至此,記憶體池的講解已完成,完整的**請到

山寨STL實現之記憶體池V2

首先,改寫obj和block結構,在obj中加入乙個域released表示這個chunk是否被釋放 1struct obj 2 1516 struct block 17 其中的callstack部分將在下一節中介紹 然後,我們增加乙個結構 1 ifdef debug 2struct use 3 7 ...

STL原始碼分析之記憶體池

前言上一節只分析了第二級配置器是由多個鍊錶來存放相同記憶體大小,當沒有空間的時候就向記憶體池索取就行了,卻沒有具體分析記憶體池是怎麼儲存空間的,是不是記憶體池真的有用不完的記憶體,本節我們就具體來分析一下 記憶體池static data template的初始化 template char defa...

高效能之記憶體池

記憶體池 memory pool 是一種記憶體分配方式。通常我們習慣直接使用new malloc等api申請分配記憶體,這樣做的缺點在於 由於所申請記憶體塊的大小不定,當頻繁使用時會造成大量的記憶體碎片並進而降低效能。記憶體池則是在真正使用記憶體之前,先申請分配一定數量的 大小相等 一般情況下 的記...