分配記憶體時如何減少記憶體碎片

2021-06-26 01:03:58 字數 2150 閱讀 9682

感覺面試的時候經常會被問到這個問題,然後我也學習了一下memcached的slab機制,發現很多伺服器都是使用這種機制來分配記憶體,所以決定學習一下。

首先,先對記憶體分配中的夥伴系統有初步的了解:

在程式設計和使用的伺服器軟體中,經常需要分配一組連續的頁框,而頻繁地申請和釋放不同大小的連續頁框,必然導致在已分配頁框的記憶體塊中分散了許多小塊的空閒頁框。這樣,即使這些頁框是空閒的,但要分配乙個大塊的連續頁框就可能無法滿足。

而linux採用了夥伴系統來解決上述難題。把所有的空閒頁框分組為11個塊鍊錶,每個塊鍊錶分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊。最大可以申請1024個連續頁框,對應4mb大小的連續記憶體。每個頁框塊的第乙個頁框的實體地址是該塊大小的整數倍。例如,大小為16個頁框的塊,其起始位址是16×212的倍數。

假設要申請乙個256個頁框的塊,先從256個頁框的鍊錶中查詢空閒塊,如果沒有,就去512個頁框的鍊錶中找,找到了則將頁框塊分為2個256個頁框的塊,乙個分配給應用,另外乙個移到256個頁框的鍊錶中。如果512個頁框的鍊錶中仍沒有空閒塊,繼續向1024個頁框的鍊錶查詢,如果仍然沒有,則返回錯誤。

頁框塊在釋放時,核心會主動將兩個互為夥伴的頁框塊合併為乙個較大的頁框塊,成功後會試圖尋找夥伴並合併為更大的記憶體塊,直至塊的大小超過上限或者沒有夥伴為止。互為夥伴的兩個記憶體塊必須符合以下條件:

1、兩個塊具有相同的大小;

2、兩個塊的實體地址連續;

3、第乙個快的實體地址是兩個塊大小的整數倍。

slab分配機制則是對夥伴演算法的改進,slab(slab allocation)的設計理念是基於物件緩衝的,基本想法是避免重複大量的初始化和清理操作。slab主要可以用於頻繁非配釋放的記憶體物件。替代malloc/free

改進的地方在於:

它對記憶體區的處理並不需要進行初始化或**。出於效率的考慮,linux並不呼叫物件的構造或析構函式,而是把指向這兩個函式的指標都置為空。linux中引入slab的主要目的是為了減少對夥伴演算法的呼叫次數。

實際上,核心經常反覆使用某一記憶體區。例如,只要核心建立乙個新的程序,就要為該程序相關的資料結構(task_struct、開啟檔案物件等)分配記憶體區。當程序結束時,收回這些記憶體區。因為程序的建立和撤銷非常頻繁,因此,linux的早期版本把大量的時間花費在反覆分配或**這些記憶體區上。從linux2.2開始,把那些頻繁使用的頁面儲存在快取記憶體中並重新使用。 可以根據對記憶體區的使用頻率來對它分類。對於預期頻繁使用的記憶體區,可以建立一組特定大小的專用緩衝區進行處理,以避免內碎片的產生。對於較少使用的記憶體區,可以建立一組通用緩衝區(如linux2.0中所使用的2的冪次方)來處理,即使這種處理模式產生碎片,也對整個系統的效能影響不大。

硬體快取記憶體的使用,又為儘量減少對夥伴演算法的呼叫提供了另乙個理由,因為對夥伴演算法的每次呼叫都會「弄髒」硬體快取記憶體,因此,這就增加了對記憶體的平均訪問次數。

slab分配模式把物件分組放進緩衝區

對於小物件, 就把slab的描述結構slab_t放在該slab中;對於大物件,則把slab結構游離出來,集中存放。關於slab中的著色區再給予具體描述:

每個slab的首部都有乙個小小的區域是不用的,稱為「著色區(coloring area)」。著色區的大小使slab中的每個物件的起始位址都按快取記憶體中的」快取行(cache line)」大小進行對齊(80386的一級快取記憶體行大小為16位元組,pentium為32位元組)。因為slab是由1個頁面或多個頁面(最多為32)組成,因此,每個slab都是從乙個頁面邊界開始的,它自然按快取記憶體的緩衝行對齊。但是,slab中的物件大小不確定,設定著色區的目的就是將slab中第乙個物件的起始位址往後推到與緩衝行對齊的位置。因為乙個緩衝區中有多個slab,因此,應該把每個緩衝區中的各個slab著色區的大小盡量安排成不同的大小,這樣可以使得在不同的slab中,處於同一相對位置的物件,讓它們在快取記憶體中的起始位址相互錯開,這樣就可以改善快取記憶體的訪問效率。

每個slab上最後乙個物件以後也有個小小的廢料區是不用的,這是對著色區大小的補償,其大小取決於著色區的大小,以及slab與其每個物件的相對大小。但該區域與著色區的總和對於同一種物件的各個slab是個常數。

每個物件的大小基本上是所需資料結構的大小。只有當資料結構的大小不與快取記憶體中的緩衝行對齊時,才增加若干位元組使其對齊。所以,乙個slab上的所有物件的起始位址都必然是按快取記憶體中的緩衝行對齊的。

參考:深入分析linux核心原始碼 

memcached 原始碼分析

記憶體分配時對齊

下面的 載自libxml中的xmlmemmory.c ifdef sun4 define align size 16 else define align size sizeof double endif define hdr size sizeof memhdr define reserve siz...

減少mysql記憶體 減少mysql記憶體占用

小站點的伺服器一般在512m或1g左右,但是我們安裝的mysql 5.6 5.7預設啟動占用記憶體400多m,mysql記憶體佔用率明顯偏高,將會導致mysql崩潰,經常出現mysql自動停止的情況。mysql的使用記憶體可以優化的。主要有兩種方法 關閉performance schema和調整ms...

編譯時的記憶體分配

程式在編譯的時候占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限.2 堆區 heap 亦稱動態記憶體分配.程式在執行的時候用mall...