python源碼分析----記憶體配置(1)

來源:互聯網
上載者:User

標籤:

上面的一篇粗略的介紹了一下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)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.