Python原始碼閱讀-記憶體管理機制(二)

wklken發表於2015-12-05

Python 的記憶體分配策略

arena

arena: 多個pool聚合的結果

arena size

pool的大小預設值位4KB

arena的大小預設值256KB, 能放置 256/4=64 個pool

obmalloc.c中程式碼

arena 結構

一個完整的arena = arena_object + pool集合

arena_object的作用

pool_headerarena_object

arena的兩種狀態

arena存在兩種狀態: 未使用(沒有建立聯絡)/可用(建立了聯絡)

全域性由兩個連結串列維護著

arena的初始化

首先, 來看下初始化相關的一些引數定義

程式碼obmalloc.c

然後, 看下obmalloc.carena初始化的程式碼

圖示: 初始化arenas陣列, 初始化後的所有arena都在unused_arena_objects單連結串列裡面

圖示: 從arenas取一個arena進行初始化

沒有可用的arena?

此時

然後, 假設第一次分配了16個, 發現沒有arena之後, 第二次處理結果: numarenas = 32

即, 陣列擴大了一倍

arena分配

new了一個全新的 arena之後,

圖示: 從全新的arena中獲取一個pool

假設arena是舊的, 怎麼分配的pool

這個arena->freepools是何方神聖?

當arena中一整塊pool被釋放的時候

也就是說, 在pool整塊被釋放的時候, 會將pool加入到arena->freepools作為單連結串列的表頭, 然後, 在從非全新arena中分配pool時, 優先從arena->freepools裡面取, 如果取不到, 再從arena記憶體塊裡面獲取

圖示

一個arena滿了之後呢

很自然, 從下一個arena中獲取

注意: 這裡有個邏輯, 就是每分配一個pool, 就檢查是不是用到了最後一個, 如果是, 需要變更usable_arenas到下一個可用的節點, 如果沒有可用的, 那麼下次進行記憶體分配的時候, 會判定從arenas陣列中取一個

arena回收

記憶體分配和回收最小單位是block, 當一個block被回收的時候, 可能觸發pool被回收, pool被回收, 將會觸發arena的回收機制

四種情況

具體可以看PyObject_Free的程式碼

記憶體分配步驟

好的, 到這裡, 我們已經知道了block和pool的關係(包括pool怎麼管理block的), 以及arena和pool的關係(怎麼從arena中拉到可用的pool)

那麼, 在分析PyObject_Malloc(size_t nbytes)如何進行記憶體分配的時候, 我們就刨除掉這些管理程式碼

關注: 如何尋找得到一塊可用的nbytes的block記憶體

其實程式碼那麼多, 定址得到對應的block也就這麼幾行程式碼, 其他程式碼都是pool沒有, 找arena, 申請arena, arena沒有, 找arenas, 最終的到一塊pool, 初始化, 返回第一個block

如果有的情況, 用現成的

從上面這個判斷邏輯來看, 記憶體分配其實主要操作的是pool, 跟arena並不是基本的操作單元(只是用來管理pool的)

結論: 進行記憶體分配和銷燬, 所有操作都是在pool上進行的

usedpools 是什麼鬼? 其實是可用pool緩衝池, 後面說

記憶體池

arena 記憶體池的大小

取決於使用者, Python提供的編譯符號, 用於決定是否控制

obmalloc.c

具體使用中, python並不直接與arenas和arena打交道, 當Python申請記憶體時, 最基本的操作單元並不是arena, 而是pool

問題: pool中所有block的size一樣, 但是在arena中, 每個pool的size都可能不一樣, 那麼最終這些pool是怎麼維護的? 怎麼根據大小找到需要的block所在的pool? => usedpools

pool在記憶體池中的三種狀態

usedpools

usedpools陣列: 維護著所有處於used狀態的pool, 當申請記憶體的時候, 會通過usedpools尋找到一塊可用的(處於used狀態的)pool, 從中分配一個block

結構:

解開看(obmalloc.c)

為了看懂這步的trick, 心好累>_

直接上圖

new一個pool時維護

init獲得的情況, 其實就是將剛剛從arena中獲取的pool加入到 usedpools 對應的雙向連結串列中, 然後初始化, 然後返回block

從現有pool中獲取block

從現有的pool, 其實就是 usedpools得到雙向連結串列頭部, 判斷是不是空連結串列, 不是的話代表有可用的pool, 直接從裡面獲取

全域性結構


先這樣吧, Python中整個記憶體池基本結構和機制大概如此, 是不是發現有好多陣列/連結串列等等, 在分配/回收上處理下做成各種池…..

後面還有記憶體相關的就是垃圾收集了, 後面再說了吧

wklken

2015-08-29

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

Python原始碼閱讀-記憶體管理機制(二) Python原始碼閱讀-記憶體管理機制(二)

相關文章