標籤:
上面的一篇粗略的介紹了一下python的對象結構,這篇來分析一個非常重要的部分,記憶體配置。。。
好像自己看的原始碼,只要是跟C語言相關的,都在記憶體處理方面做了相當多的工作。。。。例如nginx,它也有實現自己的pool,python當然也不例外。。。。
python在記憶體配置上面分成了4個層次吧。。。
_____ ______ ______ ________
[ int ] [ dict ] [ list ] ... [ string ] Python core |
+3 | <----- Object-specific memory -----> | <-- Non-object memory --> |
_______________________________ | |
[ Python‘s object allocator ] | |
+2 | ####### Object memory ####### | <------ Internal buffers ------> |
______________________________________________________________ |
[ Python‘s raw memory allocator (PyMem_ API) ] |
+1 | <----- Python memory (under PyMem manager‘s control) ------> | |
__________________________________________________________________
[ Underlying general-purpose allocator (ex: C library malloc) ]
0 | <------ Virtual memory allocated for the python process -------> |
上面是直接從原始碼裡面copy出來的注釋。。。。
(0)這個是最底層的C層面上的記憶體操作,也就是malloc和free了。。
(1)這個事python在C層面上的操作做了一層簡單的封裝。。例如PyMem_MALLOC,PyMem_FREE,它們是在malloc和free上面做了很簡單的封裝:
//這裡對malloc,realloc與free做了簡單的宏封裝,對於malloc,如果為0,然麼分配1#define PyMem_MALLOC(n)((size_t)(n) > (size_t)PY_SSIZE_T_MAX ? NULL : malloc((n) ? (n) : 1))#define PyMem_REALLOC(p, n)((size_t)(n) > (size_t)PY_SSIZE_T_MAX ? NULL : realloc((p), (n) ? (n) : 1))#define PyMem_FREEfree#endif/* PYMALLOC_DEBUG *//* * Type-oriented memory interface * ============================== * * Allocate memory for n objects of the given type. Returns a new pointer * or NULL if the request was too large or memory allocation failed. Use * these macros rather than doing the multiplication yourself so that proper * overflow checking is always done. *///通過感知type的大小來分記憶體#define PyMem_New(type, n) ( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :( (type *) PyMem_Malloc((n) * sizeof(type)) ) )#define PyMem_NEW(type, n) ( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :( (type *) PyMem_MALLOC((n) * sizeof(type)) ) )
上面還有New啥的,也就是擴充了類型大小的感知部分。。。。
(3)這一層就是最重點的部分了,PyObject_Malloc,PyObject_Free就屬於這一層。。。。python就是在這一層實現了記憶體池,用於高效的進行記憶體配置。。。。(原始碼集中在obmalloc.c裡面)
幾點python記憶體配置方面的常識:
(1)python的記憶體配置大體分為了兩個部分,首先是小記憶體配置,這裡主要是512位元組以內,以及大於512位元組的分配兩類
(2)在記憶體配置方面按照8位元組對齊的方式,例如要分配12位元組的記憶體,其實最終將會佔用16位元組的大小
* Request in bytes Size of allocated block Size class idx
* ----------------------------------------------------------------
* 1-8 8 0
* 9-16 16 1
* 17-24 24 2
* 25-32 32 3
* 33-40 40 4
* 41-48 48 5
* 49-56 56 6
* 57-64 64 7
* 65-72 72 8
* ... ... ...
* 497-504 504 62
* 505-512 512 63
上面也是直接從原始碼裡面copy出來的注釋,很鮮明的表現出了python記憶體配置方面的對齊策略。。。
接下來來看幾個非常重要的宏定義:
#define ALIGNMENT 8 /* must be 2^N */#define ALIGNMENT_SHIFT 3#define ALIGNMENT_MASK (ALIGNMENT - 1)/* Return the number of bytes in size class I, as a uint. */#define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT)#define SMALL_REQUEST_THRESHOLD 512 #define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT) //==64 //頁大小4kb#define SYSTEM_PAGE_SIZE (4 * 1024)#define SYSTEM_PAGE_SIZE_MASK (SYSTEM_PAGE_SIZE - 1)#define ARENA_SIZE (256 << 10) /* 256KB */#ifdef WITH_MEMORY_LIMITS#define MAX_ARENAS (SMALL_MEMORY_LIMIT / ARENA_SIZE)#endif //這裡定義的pool的大小為4k#define POOL_SIZE SYSTEM_PAGE_SIZE /* must be 2^N 4kb*/#define POOL_SIZE_MASK SYSTEM_PAGE_SIZE_MASK //4*1024-1
上面的宏這裡就不詳細具體的說明了,,,它主要是確定了如下的資訊
(1)一個Arena的大小為256KB(它用來管理pool)
(2)一個pool的大小為4kb
好了,接下來來看比較重要的pool的頭定義:
//記憶體池頭部//通過szidx編號可以知道當前這個pool是用來分配多大大小的記憶體的poolstruct pool_header { union { block *_padding; uint count; } ref; /* number of allocated blocks */ //當前pool上面分配的block的數量 block *freeblock; /* pool's free list head */ //指向下一個可用的block,這裡構成了一個鏈表, 它是一個離散的鏈表,很有意思 struct pool_header *nextpool; /* next pool of this size class */ //通過這兩個指標形成pool的雙鏈表 struct pool_header *prevpool; /* previous pool "" */ uint arenaindex; /* index into arenas of base adr */ //在arena裡面的索引 uint szidx; /* block size class index */ //分配記憶體的類別,8位元組,16或者。。。 uint nextoffset; /* bytes to virgin block */ //下一個可用的block的記憶體位移量 uint maxnextoffset; /* largest valid nextoffset */ //最後一個block距離開始位置的距離};
上面的注釋非常詳細的說明了各個欄位的用處。。。另外這裡可以看到有一個szidx欄位,它與上面記憶體配置時候的記憶體對齊表上的szidx相對應,其實每一個pool都是用來分配固定大小的記憶體的,例如szidx為0,那麼這個pool就是用來分配8位元組的,szidx為1就是用來分配16個位元組的。。。。這個以後看代碼就能明白。。。
這個樣子每個pool都只分配一種大小的記憶體塊就方便的多了。。特別是對於記憶體位移的計算都相當的方便
python在分配小記憶體的時候,是按照block的單位來進行分配的,例如szidx為0的pool,它的一個block大小就是8位元組。。通過freeblock指標來形成一個block的離散的單鏈表(嗯,這個實現也是非常的trick,看了好久才看明白)。。。
(嗯,其實記憶體池這部分的實現還有很多的trick,尼瑪。。。看這些trick的實現真心消耗腦細胞啊。。。擦。。只能怪自己這方面確實才疏學淺,,要看這麼久才能理解。。。)
//這個可以理解為用來管理poolstruct arena_object { /* The address of the arena, as returned by malloc. Note that 0 * will never be returned by a successful malloc, and is used * here to mark an arena_object that doesn't correspond to an * allocated arena. */ uptr address; //指向分配的256kb的首地址,這裡通過0來表明當前沒有進行分配 /* Pool-aligned pointer to the next pool to be carved off. */ block* pool_address; /* The number of available pools in the arena: free pools + never- * allocated pools. */ uint nfreepools; //可用的pool /* The total number of pools in the arena, whether or not available. */ uint ntotalpools; //在當前arena的pool的總數 /* Singly-linked list of available pools. */ struct pool_header* freepools; //pool鏈表的頭部 /* Whenever this arena_object is not associated with an allocated * arena, the nextarena member is used to link all unassociated * arena_objects in the singly-linked `unused_arena_objects` list. * The prevarena member is unused in this case. * * When this arena_object is associated with an allocated arena * with at least one available pool, both members are used in the * doubly-linked `usable_arenas` list, which is maintained in * increasing order of `nfreepools` values. * * Else this arena_object is associated with an allocated arena * all of whose pools are in use. `nextarena` and `prevarena` * are both meaningless in this case. */ struct arena_object* nextarena; struct arena_object* prevarena;};
上面這個是另外一個非常重要的結構,可以理解為它是用來管理pool的,它的address指標將會指向一個分配的256kb記憶體,pool將會在這個上面產生。。。。。
接下來先來看看Arena結構的建立過程吧:
//分配一個arena_object,其實這個也是做了緩衝的static struct arena_object*new_arena(void){ struct arena_object* arenaobj; //這裡先建立一個arena的指標 uint excess; /* number of bytes above pool alignment */ void *address; //如果新建立的話,這個用來指向申請的256KB記憶體 int err;#ifdef PYMALLOC_DEBUG if (Py_GETENV("PYTHONMALLOCSTATS")) _PyObject_DebugMallocStats();#endif if (unused_arena_objects == NULL) { //當前沒有可用的arena,那麼這裡需要建立 uint i; uint numarenas; size_t nbytes; /* Double the number of arena objects on each allocation. * Note that it's possible for `numarenas` to overflow. */ //最開始maxarenas為0,也就是說第一次建立Arena結構的時候,就將會一次性建立16個,以後直接翻倍 numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS; //INITIAL_ARENA_OBJECTS=16 if (numarenas <= maxarenas) //出現這種情況,只能說尼瑪,這都能整形溢出 啊 return NULL; /* overflow */#if SIZEOF_SIZE_T <= SIZEOF_INT if (numarenas > PY_SIZE_MAX / sizeof(*arenas)) return NULL; /* overflow */#endif nbytes = numarenas * sizeof(*arenas); //接下來分配arenas結構體所需要的記憶體 arenaobj = (struct arena_object *)realloc(arenas, nbytes); //分配記憶體位址 if (arenaobj == NULL) return NULL; arenas = arenaobj; /* We might need to fix pointers that were copied. However, * new_arena only gets called when all the pages in the * previous arenas are full. Thus, there are *no* pointers * into the old array. Thus, we don't have to worry about * invalid pointers. Just to be sure, some asserts: */ assert(usable_arenas == NULL); assert(unused_arena_objects == NULL); /* Put the new arenas on the unused_arena_objects list. */ //這裡相當於是初始化剛剛建立的arena結構體 for (i = maxarenas; i < numarenas; ++i) { arenas[i].address = 0; //通過將這個地址賦值為0,表示當前arena沒有分配可用的記憶體 arenas[i].nextarena = i < numarenas - 1 ? &arenas[i+1] : NULL; } unused_arena_objects = &arenas[maxarenas]; //這裡將unused_arena_objects指向當前可用的第一個 maxarenas = numarenas; } /* 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; //將unused_arena_objects指標指向下一個arena結構 assert(arenaobj->address == 0); //接下來分配資料記憶體#ifdef ARENAS_USE_MMAP address = mmap(NULL, ARENA_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); err = (address == MAP_FAILED);#else address = malloc(ARENA_SIZE); //這裡分配256位元組的記憶體 err = (address == 0);#endif if (err) { /* The allocation failed: return NULL after putting the * arenaobj back. */ arenaobj->nextarena = unused_arena_objects; unused_arena_objects = arenaobj; return NULL; } //將Arena結構的address指向分配的地址 arenaobj->address = (uptr)address; //更新計數器 ++narenas_currently_allocated;#ifdef PYMALLOC_DEBUG ++ntimes_arena_allocated; if (narenas_currently_allocated > narenas_highwater) narenas_highwater = narenas_currently_allocated;#endif arenaobj->freepools = NULL; //這裡pool頭部指標設定為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 = ARENA_SIZE / POOL_SIZE; //其實這裡是64個可用的pool,正好對象64中類型的小記憶體配置 assert(POOL_SIZE * arenaobj->nfreepools == ARENA_SIZE); //下面是做一次記憶體對齊,最終保證pool_address的地址是4kb的整數倍,這個主要是方便以後記憶體計算 excess = (uint)(arenaobj->address & POOL_SIZE_MASK); if (excess != 0) { //這個意思是當前的記憶體位址不是4kb的整數倍,那麼需要進行一次pool地址的對齊 --arenaobj->nfreepools; //可用數-1 arenaobj->pool_address += POOL_SIZE - excess; } arenaobj->ntotalpools = arenaobj->nfreepools; //總共可用的pool的數量 return arenaobj;}
嗯,代碼上面的注釋應該說的很清楚了吧。。。。建立結構體對象,然後分配記憶體,用adress指標來指向。。
初始化能分配的pool的數量,以及起始的地址。。。
好了。。。今天就先寫到這吧。。。好晚了。。。感覺上班還真心有點累啊。。。。。還是在學校輕鬆。。。
明天在來分析最為重要的PyObject_Malloc和PyObject_Free兩個函數吧。。。
python源碼分析----記憶體配置(1)