本文由在 c 語言的動態申請記憶體技術中,相比起[amc](
雲+社群專欄
alloc
/free
系統呼叫,記憶體池(memory pool)是與現在系統中請求一大片連續的記憶體空間,然後在執行時根據實際需要分配出去的技術。使用記憶體池的優點有:
速度遠比malloc
/free
快,因為減少了系統呼叫的次數,特別是頻繁申請/釋放記憶體塊的情況
避免了頻繁申請/釋放記憶體之後,系統的大量記憶體碎片
節省空間
根據分配出去的記憶體大小,記憶體池可以分為兩類:
每次分配出去的記憶體單元(稱為unit或者cell)的大小為程式預先定義的值。釋放記憶體塊時,則只需要簡單地掛回記憶體池煉表中即可。又稱為 「固定尺寸緩衝池」。
常規的做法是:將不同 unit size 的記憶體池整合在一起,以滿足不同記憶體塊大小的使用需求
不分配固定長度,記憶體的分配只是在一大塊空閒的記憶體上滑動。優點是分配效率很高,缺點是成批地**記憶體,因為釋放的記憶體無法直接重複利用。
使用這種需要合理規劃每塊記憶體的管理區域,所以又叫做 「基於區域的」 記憶體管理。使用這種做法的分配器,舉例有apache portable runtime中的 apr_pool 工具。本文不討論這種記憶體池。
定長記憶體池有一些基本和必要的概念,需要定義在記憶體池的結構資料中。以下命名方式使用變體的匈牙利命名法,比如nnext
,n
表示變數型別為整形。類似地,p
表示指標。
每次程式呼叫mempool_alloc
獲取乙個記憶體區域後,會獲得一塊連續的記憶體區域。管理乙個這樣的記憶體區域的單元就成為記憶體單元 unit,有時也稱作chunk。每個 unit 需要包含以下資料:
pdata
:實際的記憶體區域,其大小在建立時由呼叫方指定
乙個記憶體塊,記憶體塊中儲存著一系列的記憶體單元。
這個資料結構需要包含以下基本資訊:
nsize
:整型資料,表示該 block 在記憶體中的大小
nfree
:整型,表示剩下有幾個 unit 未被分配
乙個記憶體池總的管理資料結構,換句話說,是乙個記憶體池物件。
pblock
:指標,指向第乙個 memory block
nunitsize
:整型,表示每個 unit 的尺寸
ninitsize
:整型,表示第乙個 block 的 unit 個數
ngrowsize
:整型,表示在第乙個 block 之外再繼續增加的每個 block 的 unit 個數
建立乙個 memory pool,必須的引數為 unit size,可選引數為上文 memory pool 的ninitsize
和ngrowsize
。
銷毀整個 memory pool 並交還給作業系統。
從 memory pool 中分配乙個 unit,其尺寸是預先定義的 unit size。
釋放乙個指定的 unit。
現在我們用乙個 unit size 為 1024、init size 為 4(每乙個 block 有 4 個 units)的 memory pool 為例,解釋一下記憶體池的工作原理。下文假設整型的寬度為 4 個位元組。
程式開始,呼叫並建立乙個 memory pool。此時呼叫的函式為mempoolcreate()
,程式會建立乙個資料結構,相應的結構體成員及其取值如下:
當呼叫者第一次請求mempoolalloc()
時,記憶體池發現 block 鍊錶為空,於是想系統申請記憶體,建立 memory block,並初始化如下(其中位址值為假設值):
其中nsize = 4112 = sizeof(mempool) + ninitsize * sizeof(memunit)
。每乙個nnext
依次加一,各指代著跟著自己的下乙個 unit。最後乙個 unit 的nnext
值無意義,因此不說明其取值。
然後返回需要的 unit 中的記憶體。返回記憶體的邏輯如下:
記憶體池在 block 中查詢nfree
成員
由於nfree > 0
,表示有未分配的 unit,因此繼續在該 block 中檢視nfirst
成員
nfirst
等於 0,表示該 block 中位置為 0 的 unit 可用。因此記憶體池可以將這個 unit 中的pdata
位址返回給呼叫方。pdata
的位址值計算方式為:pblock + sizeof(memblock) + nfirst * (sizeof(memunit)) + sizeof(nnext) = 0x10010
nfree
減一
修改nfirst
的值,標記下乙個可用的 unit。注意這裡的nfirst
切切不能簡單地加一,而是取返回給呼叫方的 unit 所對應的nnext
的值,也就是下圖(2)
處原來的值1
將pdata
的位址值返回。為便於說明,這塊區域我們標記為 ca
操作後各資料結構的狀態如下:
第二次呼叫alloc
的情況類似。呼叫後各資料結構的狀態如下:
我們先看看結果:
首先程式會檢查 ca 的位址值,很快就會發現,位址 0x10010 位於上述第乙個 block 的範圍之內(0x10000 <= 0x10010 <= (0x10000 + 4112)
)。再計算偏移值可以很快得出其對應的nnext
標號,也就是上圖中的(2)
位置。
** unit,此時需要標記相應的成員值以標示 unit 的**狀態。首先檢視nfirst
的值,參見上前幅圖,nfirst
的值為 3,表示位置(3)
處的 unit 是可用的。因此我們首先把(2)
處的nnext
值設定為 3,將其加回到可用 unit 的鍊錶中
將nfirst
的值修改為0
,也就是代表剛剛**回來的 unit 的標號,而(2)
處的值賦值為 2,表示b(3)
的 unit
其實可以看到,上面就是乙個簡單的鍊錶操作。根據上面的過程,如果 cb 也釋放了的話,那麼 memory pool 的狀態則會變成這樣:
到這個時候,由於整個 block 已經完全**了(nfree == ninitsize
),那麼根據不同的策略,可以考慮將整個 block 從記憶體中釋放掉。
我們回到alloc
的邏輯中,可以看到記憶體池最開始會檢查 block 的nfree
成員。如果nfree == 0
的時候,那麼就會在該 block 的pnext
中去找到下乙個 block,再去檢查nfree
。如果發現 block 鍊錶已經結束了,那就意味著當前所有的 block 已滿,必須建立新的 block。
在實際設計中,我們需要考慮選取合適的 init size 和 grow size 值。從上面的演算法中可以看到,如果alloc
/free
呼叫非常頻繁時,第乙個 block 的使用效率是非常高的。
有些簡化的版本中,可以不使用pnext
來維護鍊錶,也就是只有乙個 block,並且記憶體的使用有乙個明確且受控的上限值。這經常用在沒有malloc
系統呼叫的 rtos 或者是一些對記憶體非常敏感的嵌入式系統中。
如果要用於多執行緒環境中,那麼 memory pool 結構體需要加上鎖
海量技術實踐經驗,盡在雲加社群!
記憶體池原理大揭秘
本文由 amc 發表於雲 社群專欄 在 c 語言的動態申請記憶體技術中,相比起alloc free系統呼叫,記憶體池 memory pool 是與現在系統中請求一大片連續的記憶體空間,然後在執行時根據實際需要分配出去的技術。使用記憶體池的優點有 速度遠比malloc free快,因為減少了系統呼叫的...
新手必看,爬蟲工作原理大揭秘
大資料時代下,資料採集推動著資料分析,資料分析推動發展。但是在這個過程中會出現很多問題。拿最簡單最基礎的爬蟲採集資料為例,過程中就會面臨,ip被封,爬取受限 違法操作等多種問題,所以在爬去資料之前,一定要了解好預爬 是否涉及違法操作,找到合適的 ip訪問 等一系列問題。當然在真正去運用之前,我們應該...
記憶體池的原理及實現
在軟體開發中,有些物件使用非常頻繁,那麼我們可以預先在堆中例項化一些物件,我們把維護這些物件的結構叫 記憶體池 在需要用的時候,直接從記憶體池中拿,而不用從新例項化,在要銷毀的時候,不是直接free delete,而是返還給記憶體池。把那些常用的物件存在記憶體池中,就不用頻繁的分配 記憶體,可以相對...