前言
現(xiàn)在開始時激動人心的時候了似芝,因為我們重要要啃下整個內(nèi)存模型中的難點之一Arenas對象蛹磺,我們前一篇文章僅得討論局限于單個內(nèi)存池锹安,當一個內(nèi)存池滿載的情況下,會由arenas對象為pymalloc_alloc調(diào)用返回其他可用的內(nèi)存池并闲。
我們先看看arenas對象在CPython源代碼中是由一個名為arena_object的結(jié)構(gòu)體定義的细睡。
//定義Arena的內(nèi)存尺寸
#define ARENA_SIZE (256 << 10) /* 256KB */
.....
struct arena_object {
//arena對象的地址,由malloc分配
uintptr_t address;
/* 指向可用內(nèi)存池的對齊指針(首個字節(jié)) */
block* pool_address;
/* arena對象托管256KB空間中可用的內(nèi)存池的數(shù)量*/
uint nfreepools;
/* arena中的內(nèi)存池總數(shù)(無論是否可用)*/
uint ntotalpools;
/* 可用池的單鏈接列表. */
struct pool_header* freepools;
/*
* 只要此arena_object不與已分配的arena關(guān)聯(lián)帝火,
* nextarena成員就用于鏈接單鏈接的“ unused_arena_objects”
* 列表中所有未關(guān)聯(lián)的arena_object溜徙。
* 在這種情況下,prevarena成員未使用犀填。
*
* 當此arena_object與具有至少一個可用池的已分配arena相關(guān)聯(lián)時蠢壹,
* 兩個成員都在雙向鏈接的“ usable_arenas”列表中使用,
* 該列表按nfreepools值的升序進行維護九巡。
*/
struct arena_object* nextarena;
struct arena_object* prevarena;
};
在Python整個進程的生命周期內(nèi)图贸,由一系列的靜態(tài)指針變量來跟蹤每個arena對象的狀態(tài),比如arenas變量管理著所有arena對象所組成的數(shù)組,并且該變量指向該數(shù)組的第一個元素的內(nèi)存地址冕广。其他的靜態(tài)變量見如下代碼所示
/* 用于跟蹤內(nèi)存塊(區(qū)域)的對象數(shù)組*/
static struct arena_object* arenas = NULL;
/*當前arenas數(shù)組中的arena對象的數(shù)量*/
static uint maxarenas = 0;
/* arena_objects.未使用的arena對象的單鏈表頭部*/
static struct arena_object* unused_arena_objects = NULL;
//與具有可用池的arenas關(guān)聯(lián)的arena_object的雙向鏈表疏日,鏈表兩端以NULL終止。
static struct arena_object* usable_arenas = NULL;
/* nfp2lasta[nfp] is the last arena in usable_arenas with nfp free pools */
static struct arena_object* nfp2lasta[MAX_POOLS_IN_ARENA + 1] = { NULL };
/* How many arena_objects do we initially allocate?
* 16 = can allocate 16 arenas = 16 * ARENA_SIZE = 4MB before growing the
* `arenas` vector.
*/
#define INITIAL_ARENA_OBJECTS 16
arena對象的初始化
接下來佳窑,我們需要知道的是CPython在進程生命周期開始時,是如何初始化我們的arena對象父能,即重講述CPython為arena對象分配內(nèi)存神凑,初始化arena對象各個字段、以及使用相關(guān)全局變量跟蹤arenas數(shù)組中各個arena對象的狀態(tài)何吝。執(zhí)行python命令時溉委,python內(nèi)部默認會連續(xù)初始化7個arena對象,如下圖所示
要窺探python內(nèi)部的運行時的內(nèi)存分配的細節(jié)爱榕,簡單粗暴的方法就是在Objects/obmalloc.c源碼文件的相關(guān)上下文插入printf函數(shù)瓣喊,然后再編譯cpython源代碼后執(zhí)行。
每當初始化一個arena對象(包含內(nèi)存分配和字段賦值)時黔酥,其初始化過程的函數(shù)棧的調(diào)用順序如下圖藻三,new_arena是一個屬于內(nèi)存模型第2層的函數(shù),PyMem_RawRealloc和_PyMem_RawRealloc都屬于第1層的函數(shù)接口跪者。第1層和第2層的函數(shù)接口我在前面的篇章已經(jīng)說得很清楚棵帽。
這里重點講解第一個arena對象初始化的過程≡幔看一下struct arena_object* new_arena(void)函數(shù),這里是不會討論有關(guān)調(diào)試模式的arena內(nèi)存分配逗概,因為new_arena函數(shù)代碼篇幅比較長,我們不妨分成將代碼的上下文分成三段來分析忘衍。這里先看一下Objects/obmalloc.c的第1243行到1276行
#define INITIAL_ARENA_OBJECTS 16
....
static struct arena_object*
new_arena(void)
{
struct arena_object* arenaobj;
uint excess; /* number of bytes above pool alignment */
void *address;
static int debug_stats = -1;
//debug模式的相關(guān)代碼不用理會
if (debug_stats == -1) {
const char *opt = Py_GETENV("PYTHONMALLOCSTATS");
debug_stats = (opt != NULL && *opt != '\0');
}
if (debug_stats)
_PyObject_DebugMallocStats(stderr);
if (unused_arena_objects == NULL) {
uint i;
uint numarenas;
size_t nbytes;
/*
將每次分配的arena對象數(shù)量增加一倍
每次新的arena對象內(nèi)存逾苫,都需要必要內(nèi)存溢出檢測卿城。
*/
numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS;
if (numarenas <= maxarenas)
return NULL; /* overflow */
#if SIZEOF_SIZE_T <= SIZEOF_INT
if (numarenas > SIZE_MAX / sizeof(*arenas))
return NULL; /* overflow */
#endif
/*向第1層API申請內(nèi)存*/
nbytes = numarenas * sizeof(*arenas);
arenaobj = (struct arena_object *)PyMem_RawRealloc(arenas, nbytes);
if (arenaobj == NULL)
return NULL;
arenas = arenaobj;
.....
}
從上面的代碼可以知道,arena對象的內(nèi)存分配是作為一個動態(tài)數(shù)組的連續(xù)元素的內(nèi)存尺寸向C底層的realloc函數(shù)申請的铅搓。每當下一次新的arena對象是批量申請的的內(nèi)存分配量是前一次2倍瑟押,比如arena對象內(nèi)存初始化默認就是的數(shù)量是16×sizeof(struct arena_object),意味著申請的堆內(nèi)存空間是連續(xù)的,能夠容納16個arena_object結(jié)構(gòu)體狸吞,下一次申請內(nèi)存量就是32×sizeof(struct arena_object)勉耀,意味著能夠容納32個arena_object結(jié)構(gòu)體。
如此類推如果當前內(nèi)存分配量為N蹋偏,那么下一次的內(nèi)存分配量為2N便斥。
備注:arenas本身是一個struct arena_object的指針,因此代碼中的sizeof(*arenas)實際上等價于sizeof(struct arena_object)威始。
這里再看一下new_arena函數(shù)位于Objects/obmalloc.c的第1284行到1297行枢纠,下面代碼對arenas數(shù)組的某些字段以及unused_arena_objects變量執(zhí)行初始化
#define INITIAL_ARENA_OBJECTS 16
....
static struct arena_object*
new_arena(void)
{
.....
/*
只有當前arena中的所有頁面(pool)都已滿時,
才會調(diào)用new_arena()黎棠。因此晋渺,沒有指向舊數(shù)組的指針。
因此脓斩,我們不必擔心指針無效木西。當然可以增加一些斷言:
*/
assert(usable_arenas == NULL);
assert(unused_arena_objects == NULL);
/*將新的arena放在unused_arena_objects列表中*/
for (i = maxarenas; i < numarenas; ++i) {
arenas[i].address = 0; /*標記為未關(guān)聯(lián)*/
arenas[i].nextarena = i < numarenas - 1 ?
&arenas[i+1] : NULL;
}
/* Update globals. */
unused_arena_objects = &arenas[maxarenas];
maxarenas = numarenas;
}
.....
}
每次新增的arena對象數(shù)組會由靜態(tài)變量arenas托管,并且新增的arena對象元素随静,都需要對每個新的arena對象的address字段做0初始化八千,這用于標識每個新增的arena對象未被關(guān)聯(lián)
未關(guān)聯(lián)的arena對象,意味著該arena對象還沒被使用,由unused_arena_objects指針以一個單向鏈表的形式負責跟蹤其狀態(tài)燎猛,如下圖所示恋捆。
需要注意的是每次內(nèi)存分配新增的arena對象元素,unused_arena_object指針始終都以指向&arenas[maxarenas]為目標重绷,也即是隨著整個arenas數(shù)組的增長會,unused_arena_object指針會跟隨如下表maxarenas索引位置變化移動沸停。
現(xiàn)在看一下位于Objects/obmalloc.c的第1300行到1331行的代碼
static struct arena_object*
new_arena(void)
{
......
/*
對應源代碼的第1300行到1302行
將下一個可用的[arena]對象從unused_aren_objects列表的開頭移出。
*/
assert(unused_arena_objects != NULL);
arenaobj = unused_arena_objects;
unused_arena_objects = arenaobj->nextarena;
/**/
assert(arenaobj->address == 0);
//對應源代碼第1304行
address = _PyObject_Arena.alloc(_PyObject_Arena.ctx, ARENA_SIZE);
if (address == NULL) {
/* The allocation failed: return NULL after putting the
* arenaobj back.
*/
arenaobj->nextarena = unused_arena_objects;
unused_arena_objects = arenaobj;
return NULL;
}
arenaobj->address = (uintptr_t)address;
++narenas_currently_allocated;
++ntimes_arena_allocated;
if (narenas_currently_allocated > narenas_highwater)
narenas_highwater = narenas_currently_allocated;
arenaobj->freepools = NULL;
/* pool_address <- first pool-aligned address in the arena
nfreepools <- number of whole pools that fit after alignment */
arenaobj->pool_address = (block*)arenaobj->address;
arenaobj->nfreepools = MAX_POOLS_IN_ARENA;
excess = (uint)(arenaobj->address & POOL_SIZE_MASK);
if (excess != 0) {
--arenaobj->nfreepools;
arenaobj->pool_address += POOL_SIZE - excess;
}
arenaobj->ntotalpools = arenaobj->nfreepools;
return arenaobj;
}
當執(zhí)行到位于第1300行到1302行昭卓,實際上就是將第一個arena對象從unused_arena_objects單鏈表中彈出愤钾,并且unused_arena_objects指針指向了arenas數(shù)組的第2個元素的內(nèi)存地址(例如:0x55BAB2677860),意味著從unused_arena_objects鏈表移出的arenas[0]元素最終會交由usable_arenas指針所接管(下文會談?wù)摰?.
其中執(zhí)行到1304行的源代碼,從這條語句開始表明劃出的arena對象開始初始化內(nèi)存池候醒,
address = _PyObject_Arena.alloc(_PyObject_Arena.ctx, ARENA_SIZE);
ARENA_SIZE這個宏其實表示ARENA的有效負載是256KB绰垂,一個pool的固定尺寸是4KB,而一個arena對象就托管著64個pool火焰,這些概念清楚的話劲装,那你就知道下面代碼的用意。
查看第434行到440行的源代碼,其中靜態(tài)變量_PyObject_Arena變量根據(jù)系統(tǒng)平臺選擇C底層的堆內(nèi)存分配器來執(zhí)行初始化,對于Linux系統(tǒng)來說占业,這里就是_PyObject_ArenaMalloc,_PyObject_ArenaFree這兩個函數(shù)指針
static PyObjectArenaAllocator _PyObject_Arena = {NULL,
#ifdef MS_WINDOWS
_PyObject_ArenaVirtualAlloc, _PyObject_ArenaVirtualFree
#elif defined(ARENAS_USE_MMAP)
_PyObject_ArenaMmap, _PyObject_ArenaMunmap
#else
_PyObject_ArenaMalloc, _PyObject_ArenaFree
#endif
};
那么上面的代碼實際上等價于
static PyObjectArenaAllocator _PyObject_Arena = {
NULL,
_PyObject_ArenaMalloc,
_PyObject_ArenaFree
};
其中_PyObject_Arena的具體定義是PyObjectArenaAllocator類型,這種編程模式很熟悉吧绒怨,就是第一篇說過的描述內(nèi)存塊分配器的簡單類,。其源代碼位于Include/cpython/objimpl.h的第97行到106行
typedef struct {
/* user context passed as the first argument to the 2 functions */
void *ctx;
/* allocate an arena of size bytes */
void* (*alloc) (void *ctx, size_t size);
/* free an arena */
void (*free) (void *ctx, void *ptr, size_t size);
} PyObjectArenaAllocator;
那么說了那么多其實第1304行的語句實際上舊調(diào)用了_PyObject_ArenaMalloc函數(shù)接口來為arena對象分配額外256KB的空間谦疾,其底層就是對malloc的封裝南蹂。
返回第一個arena對象時并且成功為64個pool分配內(nèi)存,就是源代碼從第1304行到1331行所做的事情念恍,內(nèi)存示意圖如下:
allocate_from_new_pool函數(shù)
我們了解到arenas數(shù)組中的的第一個arena對象的內(nèi)存分配以及該arena對象為其所托管的池集合的內(nèi)存分配六剥,所有這些細節(jié)后。現(xiàn)在將注意力返回到arena對象初始化過程中的allocate_from_new_pool函數(shù)峰伙,即在Objects/obmalloc.c源文件的第1453行到第1579行,來查看它初始化后的第一個arena對象的運行時的內(nèi)存狀態(tài)
從第1466行可知即由new_arena函數(shù)返回的是從unused_arena_objects鏈表劃出的arena對象疗疟。下面代碼是第1466行-第1521行的代碼。
static void*
allocate_from_new_pool(uint size)
{
/* There isn't a pool of the right size class immediately
* available: use a free pool.
*/
if (UNLIKELY(usable_arenas == NULL)) {
/* No arena has a free pool: allocate a new arena. */
#ifdef WITH_MEMORY_LIMITS
if (narenas_currently_allocated >= MAX_ARENAS) {
return NULL;
}
#endif
usable_arenas = new_arena(); //源代碼的第1466行
if (usable_arenas == NULL) {
return NULL;
}
usable_arenas->nextarena = usable_arenas->prevarena = NULL;
assert(nfp2lasta[usable_arenas->nfreepools] == NULL);
nfp2lasta[usable_arenas->nfreepools] = usable_arenas;
}
assert(usable_arenas->address != 0);
/*
arena已經(jīng)具有最小的nfreepools值瞳氓,因此減少nfreepools不會改變該值策彤,
并且我們不需要重新排列usable_arenas列表。
但是匣摘,如果arena完全被分配(池集合完全使用中)店诗,
則需要從usable_arenas中刪除其arena對象。
*/
assert(usable_arenas->nfreepools > 0); //源代碼行1481行
if (nfp2lasta[usable_arenas->nfreepools] == usable_arenas) {
/* It's the last of this size, so there won't be any. */
nfp2lasta[usable_arenas->nfreepools] = NULL;
}
/* If any free pools will remain, it will be the new smallest. */
if (usable_arenas->nfreepools > 1) {
assert(nfp2lasta[usable_arenas->nfreepools - 1] == NULL);
nfp2lasta[usable_arenas->nfreepools - 1] = usable_arenas;
}
/*
嘗試獲取一個閑置的內(nèi)存池音榜,由于usable_arenas->freepools為NULL
if語句塊對應源代碼文件的第1494行到1521行的代碼是不會被執(zhí)行的.
*/
poolp pool = usable_arenas->freepools;
if (LIKELY(pool != NULL)) {
/* Unlink from cached pools. */
usable_arenas->freepools = pool->nextpool;
usable_arenas->nfreepools--;
if (UNLIKELY(usable_arenas->nfreepools == 0)) {
/* Wholly allocated: remove. */
assert(usable_arenas->freepools == NULL);
assert(usable_arenas->nextarena == NULL ||
usable_arenas->nextarena->prevarena ==
usable_arenas);
usable_arenas = usable_arenas->nextarena;
if (usable_arenas != NULL) {
usable_arenas->prevarena = NULL;
assert(usable_arenas->address != 0);
}
}
else {
assert(usable_arenas->freepools != NULL ||
usable_arenas->pool_address <=
(block*)usable_arenas->address +
ARENA_SIZE - POOL_SIZE);
}
}
else {
/*
目前會執(zhí)行該語句塊的代碼庞瘸,對應源代碼的第154行到1545行
從內(nèi)存池集合劃出4KB的內(nèi)存空間,用于初始化新的內(nèi)存池. */
assert(usable_arenas->nfreepools > 0);
assert(usable_arenas->freepools == NULL);
pool = (poolp)usable_arenas->pool_address;
//內(nèi)存池集合的有效內(nèi)存區(qū)(邊界)檢測
assert((block*)pool <= (block*)usable_arenas->address +
ARENA_SIZE - POOL_SIZE);
//計算當前內(nèi)存池pool的索引值
pool->arenaindex = (uint)(usable_arenas - arenas);
assert(&arenas[pool->arenaindex] == usable_arenas);
//DUMMY_SIZE_IDX,目前搞不懂用意何在?
pool->szidx = DUMMY_SIZE_IDX;
//將當前可用的arena對象的pool_address指向下一個內(nèi)存池的首個字節(jié)赠叼。
usable_arenas->pool_address += POOL_SIZE;
//遞減當前arena對象的nfreepools計數(shù)器擦囊。
--usable_arenas->nfreepools;
/*
對應源代碼的第1535行的代碼到1544行的代碼
如果當前usable_arenas指針所指向的arena對象的nfreepools大于0
當前if語句內(nèi)的代碼并沒執(zhí)行,對應源代碼的第1535行到1545行梅割。
*/
if (usable_arenas->nfreepools == 0) {
//斷定usable_arena指向usable_arenas鏈表的
//下一個arena對象的條件
assert(usable_arenas->nextarena == NULL ||
usable_arenas->nextarena->prevarena ==
usable_arenas);
/* Unlink the arena: it is completely allocated. */
usable_arenas = usable_arenas->nextarena;
if (usable_arenas != NULL) {
usable_arenas->prevarena = NULL;
assert(usable_arenas->address != 0);
}
}
}
.....
}
值得注意的是霜第,全局struct arena_object類型的指針nfp2lasta在初始化時是一個包含65個NULL指針為元素的數(shù)組葛家。
static struct arena_object* nfp2lasta[MAX_POOLS_IN_ARENA + 1] = { NULL };
nfplasta是一個用于記錄usable_arenas雙重鏈表具有空閑池(free pools)的最近一個可用arena對象户辞,當然是記錄arenas數(shù)組中元素對應的內(nèi)存地址。
從上面代碼可知癞谒,對于開辟用于初始化的arenas[0]中的第一個內(nèi)存池來說底燎,內(nèi)存狀態(tài)如下圖,目前第1535行的代碼到1544行的代碼是不會被執(zhí)行的,我后文會在再提及弹砚。我這里先給執(zhí)行到源代碼的第1466行到1533行的內(nèi)存示意圖,這是CPython初始化第一個arena對象以及初始化其托管256KB空間中低地址端中的第一個內(nèi)存池的狀態(tài)双仍。如下圖是初始化第一個arena對象后的內(nèi)存狀態(tài),我們說usable_arenas指針負責維護一個雙重鏈表桌吃,那么從下圖可以看出
- usable_arenas雙鏈表:NULL?arenas[0]?NULL
- unused_arena_objects單鏈表:arenas[1]→arenas[2]→....→arenas[14]→NULL
了解CPython初始化第一個arena對象以及初始化其托管256KB空間中低地址端中的第一個內(nèi)存池的狀態(tài)過程朱沃,我認為很重要,這是整個arena級別到pool級別內(nèi)存對象分配的起點《何铮基于目前往上其他同類文章的CPython內(nèi)存模型的源碼解讀搬卒,沒有這個詳細的過程描述,那么我這篇文章是他們內(nèi)容最好的補充翎卓。
在CPython主循環(huán)的驅(qū)動下契邀,arenas數(shù)組中7個arenas對象初始化過程是一個非常復雜的過程。不僅包含每個aren對象托管的內(nèi)存池集合中pool的內(nèi)存分配和初始化失暴。還有包含CPython相關(guān)加載模塊的其他內(nèi)部Python對象的內(nèi)存分配至各個已初始化的內(nèi)存池中坯门。為簡化該過程,伴隨arena初始化其他內(nèi)部Python對象內(nèi)存分配這里不做討論逗扒。
接下來從第一個arena對象到第七個arena對象在初始化過程中古戴,我們抽取一些特殊細節(jié)做些說明。
在第一個arena對象托管的內(nèi)存池集合空間(256KB)里缴阎,初始化第一個內(nèi)存池后允瞧,如下圖如此類推...第2個、第3個蛮拔、......你們發(fā)現(xiàn)到什么細節(jié)呢述暂?
首先、我們說同一個arena對象所托管的不同內(nèi)存池建炫,這些已初始化的內(nèi)存池具有不同的size class idx畦韭,也就是同一個arena對象托管的不同內(nèi)存池的size class idx是不一樣的,它們的變化根據(jù)外部CPython代碼的內(nèi)存請求而定,例如內(nèi)存池這一時刻的size class idx是3,下一個時刻在內(nèi)存池的塊都被回收后肛跌,size class idx可能變?yōu)?1.
然后艺配、nf2lasta,你們會發(fā)現(xiàn)每次調(diào)用allocate_from_new_pool(uint size),全局靜態(tài)數(shù)組的nf2lasta的下標變化和當前usable_arenas所指向的arena對象(此時是arenas數(shù)組中的第一個元素arenas[0])中的nfreepools計數(shù)器是實時同步的,nf2lasta數(shù)組其實是整個arenas數(shù)組狀態(tài)的跟蹤器衍慎,通過其下標和及其引用追蹤到usable_arenas當前所指向的arena對象转唉。
arena對象后續(xù)初始化
上面再來看看當前arena所托管的內(nèi)存池集合滿載狀態(tài)(fulled)的情景,也就是說內(nèi)存池集合所有內(nèi)存池的狀態(tài)都是正在使用(using)稳捆,見如下圖
此時的情景是第一個arena對象所托管內(nèi)池集合中的最后一個內(nèi)存池的內(nèi)存地址赠法,這里假設(shè)是140628899726336,也就是當前arenas[0]所托管的內(nèi)存池集合區(qū)域的末端邊界的地址為140628899730432,而當前arenas[0]的pool_address所指向的地址剛好也是該內(nèi)存地址乔夯。我們這里先拋出一個問題砖织。
pool_address指針偏移問題:可以這么認為后續(xù)的每個arena對象所托管的內(nèi)存池集合,他們都位于一塊連續(xù)的堆內(nèi)存空間嗎?
答案:是否定的末荐,我們在下面講解第2個后續(xù)的arena初始化時會印證一點侧纯。事實上我們要指出的是arena對象的pool_address指針是指向池集合空間內(nèi)下一個要初始化的內(nèi)存池的起始邊界,隨著初始化到第64個內(nèi)存池,pool_address指針就指向第64個內(nèi)存池末端的邊界。
在CPython啟動時甲脏,主循環(huán)自第二次調(diào)用allocate_from_new_pool(uint size)眶熬,跟蹤arenas的幾個全局靜態(tài)變量已經(jīng)發(fā)生變化妹笆,此時與arena對象有關(guān)的全局靜態(tài)計數(shù)器的
- unused_arena_objects!=NULL
- usable_arenas=NULL
- narenas_currently_allocated=1
- ntimes_arena_allocated=1
意味著allocate_from_new_pool調(diào)用new_arena函數(shù)內(nèi)部的代碼上下文和第一次發(fā)生變化,如下代碼所示,這個代碼我不需要再解析娜氏。
static struct arena_object*
new_arena(void)
{
//由于unused_arena_objects并不為NULL,第一個if分支會跳過
if (unused_arena_objects == NULL) {
....
}
/* Take the next available arena object off the head of the list. */
assert(unused_arena_objects != NULL);
arenaobj = unused_arena_objects;
unused_arena_objects = arenaobj->nextarena;
assert(arenaobj->address == 0);
//為當前arena對象分配256KB的內(nèi)存池集合
address = _PyObject_Arena.alloc(_PyObject_Arena.ctx, ARENA_SIZE);
if (address == NULL) {
/* The allocation failed: return NULL after putting the
* arenaobj back.
*/
arenaobj->nextarena = unused_arena_objects;
unused_arena_objects = arenaobj;
return NULL;
}
arenaobj->address = (uintptr_t)address;
//遞增各個靜態(tài)的計數(shù)器
++narenas_currently_allocated;
++ntimes_arena_allocated;
if (narenas_currently_allocated > narenas_highwater)
narenas_highwater = narenas_currently_allocated;
arenaobj->freepools = NULL;
/* pool_address <- first pool-aligned address in the arena
nfreepools <- number of whole pools that fit after alignment */
arenaobj->pool_address = (block*)arenaobj->address;
arenaobj->nfreepools = MAX_POOLS_IN_ARENA;
excess = (uint)(arenaobj->address & POOL_SIZE_MASK);
if (excess != 0) {
--arenaobj->nfreepools;
arenaobj->pool_address += POOL_SIZE - excess;
}
arenaobj->ntotalpools = arenaobj->nfreepools;
return arenaobj;
}
現(xiàn)在先來看一下是第二個arena對象其內(nèi)存池集合中第一個內(nèi)存池初始化后的情景圖晾浴,我們查看一下如下圖第二個內(nèi)存池的地址邊界,和上面圖例的第一個內(nèi)存池集合的邊界是不一樣的牍白。每個arena對象托管的內(nèi)存池集合的堆內(nèi)存區(qū)域都是分散在堆內(nèi)存中脊凰。
usable_arenas雙鏈表
請讀者細心想一下,usable_arenas這個雙鏈表在第一個arena對象滿載后,偏移至第二個arenas對象茂腥,換句話說狸涌,當一個arena對象它所托管的內(nèi)存池集合中的所有內(nèi)存池的狀態(tài)都正在使用(using),那么CPython會將該arena對象從usable_arenas鏈表中刪除最岗,源代碼見Objects/obmalloc.c的第1498行到1509行
static void*
allocate_from_new_pool(uint size)
{
....
if (usable_arenas->nfreepools == 0) {
assert(usable_arenas->nextarena == NULL ||
usable_arenas->nextarena->prevarena ==
usable_arenas);
/* Unlink the arena: it is completely allocated. */
usable_arenas = usable_arenas->nextarena;
if (usable_arenas != NULL) {
usable_arenas->prevarena = NULL;
assert(usable_arenas->address != 0);
}
}
....
}
最后帕胆,在CPython的7個arena對象初始化完成后,它們托管的內(nèi)存池集合都完全分配了般渡,如下圖所示
小結(jié)
上面的所有圖例都是從源代碼上下文插入各個關(guān)鍵位置插入printf函數(shù)懒豹,在編譯執(zhí)行后,根據(jù)打印出來的關(guān)鍵信息繪制出來的驯用。最大程度上呈現(xiàn)了CPython運行時的內(nèi)存模型脸秽。你會發(fā)現(xiàn)我多次強調(diào)不需要理會CPython源代碼中和Debug模式相關(guān)的代碼,因為那是在CPython編譯后給IDE調(diào)試時查看內(nèi)存信息所使用的蝴乔。由于通過IDE查看的信息可能在堆和棧中參雜了不同程度的調(diào)試信息或調(diào)試時內(nèi)存計數(shù)器记餐。這樣你通過IDE查看的內(nèi)存數(shù)據(jù)繪制內(nèi)存模型圖例,可能和CPython運行時出現(xiàn)差異薇正。
因此片酝,我鼓勵把你自己的大腦當成一個IDE編譯器,用你的大腦來理解CPython源代碼挖腰。而不是通過IDE雕沿,只要你多嘗試自己去閱讀,并且對一些比較晦澀難懂的代碼猴仑,插入一些測試性代碼审轮,用一些簡單的數(shù)據(jù)做運行時演算。時刻記得宁脊,printf函數(shù)是你的好朋友断国。有助于提高你對CPython源代碼的理解贤姆。
這里遺留一些問題榆苞,源代碼的第1548行到1565行的內(nèi)容有些操作涉及usedpool(內(nèi)存池緩沖陣列),我打算留到下一篇再補充
//源代碼的1548行到1565行
/* Frontlink to used pools. */
block *bp;
poolp next = usedpools[size + size]; /* == prev */
pool->nextpool = next;
pool->prevpool = next;
next->nextpool = pool;
next->prevpool = pool;
pool->ref.count = 1;
if (pool->szidx == size) {
/* Luckily, this pool last contained blocks
* of the same size class, so its header
* and free list are already initialized.
*/
bp = pool->freeblock;
assert(bp != NULL);
pool->freeblock = *(block **)bp;
return bp;
}
更新待續(xù)……