日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Python內(nèi)存管理介紹

CPython內(nèi)存管理器

CPython源碼包的功能分類

此文是按照源碼Python3.9來寫,其中有些assert語句與一些不必要的宏字段會刪除,保留核心的邏輯并添加注釋,方便自己和大家理解。在代碼中都會注明源碼出處方便大家完整閱讀。

為哈巴河等地區(qū)用戶提供了全套網(wǎng)頁設計制作服務,及哈巴河網(wǎng)站建設行業(yè)解決方案。主營業(yè)務為成都網(wǎng)站建設、網(wǎng)站設計、哈巴河網(wǎng)站設計,以傳統(tǒng)方式定制建設網(wǎng)站,并提供域名空間備案等一條龍服務,秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務。我們深信只要達到每一位用戶的要求,就會得到認可,從而選擇與我們長期合作。這樣,我們也可以走得更遠!

目錄 概要
Demo 采用了Python的演示應用程序
Doc 文檔
Grammer Python的語法文件
Include 編譯Python時引用的各種頭文件
Lib 標準附加庫
Mac Mac用的工具等
Misc 很多文件的集合(如gdbinit和vimrc等)
Modules Python的C語言擴展模塊
Objects Python的對象用的C語言代碼
PC 依存于OS等環(huán)境的程序
PCbuild 構(gòu)造Win32和x64時使用
Parser Python用的解析器
Python Python的核心

Python的內(nèi)存管理架構(gòu)

Python是一門動態(tài)的、一切皆對象的語言,這些內(nèi)存申請可能會產(chǎn)生大量小的內(nèi)存,為了加快內(nèi)存操作和減少內(nèi)存碎片化,使用Python自己的內(nèi)存管理器,叫PyMalloc。

 
 
 
 
  1. # Objects/obmalloc.c 代碼注釋 
  2. /* An object allocator for Python. 
  3.    Here is an introduction to the layers of the Python memory architecture, 
  4.    showing where the object allocator is actually used (layer +2), It is 
  5.    called for every object allocation and deallocation (PyObject_New/Del), 
  6.    unless the object-specific allocators implement a proprietary allocation 
  7.    scheme (ex.: ints use a simple free list). This is also the place where 
  8.    the cyclic garbage collector operates selectively on container objects. 
  9.     Object-specific allocators 
  10.     _____   ______   ______       ________ 
  11.    [ int ] [ dict ] [ list ] ... [ string ]       Python core         | 
  12. +3 | <----- Object-specific memory -----> | <-- Non-object memory --> |    # 對象特有的內(nèi)存分配器 
  13.     _______________________________       |                           | 
  14.    [   Python's object allocator   ]      |                           | 
  15. +2 | ####### Object memory ####### | <------ Internal buffers ------> |    # Python對象分配器 
  16.     ______________________________________________________________    | 
  17.    [          Python's raw memory allocator (PyMem_ API)          ]   | 
  18. +1 | <----- Python memory (under PyMem manager's control) ------> |   |      # Python低級內(nèi)存分配器 
  19.     __________________________________________________________________ 
  20.    [    Underlying general-purpose allocator (ex: C library malloc)   ] 
  21.  0 | <------ Virtual memory allocated for the python process -------> |      # 通用的基礎分配器(如glibc的malloc等)                                    
  22.     ========================================================================= 
  23.     _______________________________________________________________________ 
  24.    [                OS-specific Virtual Memory Manager (VMM)               ] 
  25. -1 | <--- Kernel dynamic storage allocation & management (page-based) ---> |  # OS特有的虛擬內(nèi)存管理器 
  26.     __________________________________   __________________________________ 
  27.    [                                  ] [                                  ] 
  28. -2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> |  # 物理內(nèi)存和交換目的地(如HDD等) 
  29. */ 
 
 
 
 
  1. PyDict_New()               // 第三層 
  2.  PyObject_GC_New()     // 第二層 
  3.   PyObject_Malloc()     // 第二層 
  4.    new_arena()       // 第一層 
  5.    malloc()        // 第零層 
  6. ////////////////////////////////////////以下2層屬于操作系統(tǒng)范疇,不在討論范圍/////////////////////////////////

圖1

通用的基礎分配器(0層)

512字節(jié)是CPython的閾值 

 
 
 
 
  1. //Objects/obmalloc.c 
  2. #define SMALL_REQUEST_THRESHOLD 512 
  3. #define NB_SMALL_SIZE_CLASSES   (SMALL_REQUEST_THRESHOLD / ALIGNMENT) 
  4. /* Largest positive value of type Py_ssize_t. */ 
  5. #define PY_SSIZE_T_MAX ((Py_ssize_t)(((size_t)-1)>>1)) 
  6. static void *
  7.  _PyObject_Malloc(void *ctx, size_t nbytes) 
  8. {  // 走Python的分配器,函數(shù)進去就會有判斷(0,512]的才使用 
  9.     void* ptr = pymalloc_alloc(ctx, nbytes); 
  10.     if (LIKELY(ptr != NULL)) { 
  11.         return ptr; 
  12.     } 
  13.   // 大于512字節(jié)走C的malloc,函數(shù)進去進做了越界判斷,Py_ssize_t為閾值 
  14.     ptr = PyMem_RawMalloc(nbytes); 
  15.     if (ptr != NULL) { 
  16.         raw_allocated_blocks++; 
  17.     } 
  18.     return ptr; 
  19. }
  •  0: 直接調(diào)用 malloc 函數(shù)
  •  1 ~ 512: 由Python的內(nèi)存池負責分配,內(nèi)存池以內(nèi)存尺寸進行劃分
  •  512以上: 直接調(diào)動 malloc 函數(shù)

在源代碼中以PyMem_為前綴的所有函數(shù)是封裝C語言提供給Python語法使用的,其核心使用的就是第0層malloc之類的C庫函數(shù)。

通常Python沒有對小塊內(nèi)存的內(nèi)存池的大小做任何的限制

當Python在WITH_MEMORY_LIMITS編譯符號打開的背景下進行編譯時,Python內(nèi)部的另一個符號會被激活,這個名為SMALL_MEMORY_LIMIT的符號限制了整個內(nèi)存池的大小,同時,也就限制了可以創(chuàng)建的arena的個數(shù)。

在默認情況下,不論是Win32平臺,還是unix平臺,這個編譯符號都是沒有打開的,所以通常Python都沒有對小塊內(nèi)存的內(nèi)存池的大小做任何的限制。

 
 
 
 
  1. [obmalloc.c] 
  2. #ifdef WITH_MEMORY_LIMITS 
  3. #ifndef SMALL_MEMORY_LIMIT 
  4. #define SMALL_MEMORY_LIMIT  (64 * 1024 * 1024)  /* 64 MB -- more? */ 
  5. #endif 
  6. #endif 
  7. #ifdef WITH_MEMORY_LIMITS 
  8. #define MAX_ARENAS      (SMALL_MEMORY_LIMIT / ARENA_SIZE) 
  9. #endif

CPython讓我們只需要提供類型和數(shù)量

有了以下的宏定義,我們寫代碼的時候只需要提供類型和數(shù)量,而不用自己去計算具體需要申請多少空間

 
 
 
 
  1. //Include/pymem.h 
  2. #define PyMem_New(type, n) \ 
  3.   ( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :      \ 
  4.         ( (type *) PyMem_Malloc((n) * sizeof(type)) ) ) 
  5. #define PyMem_NEW(type, n) \ 
  6.   ( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :      \ 
  7.         ( (type *) PyMem_MALLOC((n) * sizeof(type)) ) )
  8.  #define PyMem_Resize(p, type, n) \ 
  9.   ( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :        \ 
  10.         (type *) PyMem_Realloc((p), (n) * sizeof(type)) ) 
  11. #define PyMem_RESIZE(p, type, n) \ 
  12.   ( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :        \ 
  13.         (type *) PyMem_REALLOC((p), (n) * sizeof(type)) ) 
  14. #define PyMem_Del               PyMem_Free
  15. #define PyMem_DEL               PyMem_FREE

內(nèi)存碎片問題

每次申請內(nèi)存的時候一定不會每次都遇到剛好的塊去分配,那么一下一大塊內(nèi)存會被切割使用,那么中間會產(chǎn)生很多小的但是可能不在會被使用的碎片(但是整個加起來也是一個大的可使用的塊),而且每次查找合適的塊需要遍歷整個堆,所以為了減少碎片和快速分配內(nèi)存,我們需要內(nèi)存管理。

圖2

Python內(nèi)存管理的劃分

小于512字節(jié)的內(nèi)存申請由Python的低級分配器接管(空白內(nèi)存,raw memory),做了3級層次的劃分,依次為block、pool、arena

  •  block是Python內(nèi)存管理的最小單元,其中他的大小與pool_head的szidx一致,而且采用的Best-fit分配策略
    •   Best-fit分配策略:返回大于等于 size 的最小分塊
  •  pool是管理一類規(guī)格的block,是具有size概念的內(nèi)存管理抽象體,有pool_head的一個szidx管理。(當然她還有狀態(tài)的管理后面會介紹)
  •  arena是可以管理多個pool,每個pool的規(guī)格可以各不相同。(他也有自己的狀態(tài)管理后面會介紹)

圖3

pool與arena頭與boby連接的不同

圖4

Python低級內(nèi)存分配器(1層)

現(xiàn)在來到的是真正Python的內(nèi)存管理談論的部分了,Python內(nèi)存管理做了哪些處理

  •  減少內(nèi)存碎片的問題
    •   上面的block概念的提出,是為了有效改善內(nèi)存碎片的問題,但是不可能解決的
  • 不可能讓每次分配都遍歷整個堆
    •   所以arena_head、pool_head都比較復雜,其中都維護了多條鏈表來把開銷從O(N)降低到O(1)
  •  Python分配器主要是處理<512字節(jié)小內(nèi)存,頻繁的分配/釋放一定是會浪費
    •   Python的大部分基礎類引入了緩存池的機制用于管理小塊內(nèi)存的申請和釋放,提供pymalloc_alloc、pymalloc_realloc、pymalloc_free三個接口
      •   比如字典有80大小的數(shù)組作為緩存池
      •   列表也有80大小的數(shù)組作為緩存池

arena

    “    第一層的核心就是創(chuàng)建arena

arena的大小

arena的默認值是256K

 
 
 
 
  1. #define ARENA_BITS              18                    /* 256 KiB */ 
  2. #define ARENA_SIZE              (1 << ARENA_BITS)

arena頭結(jié)構(gòu)體 

 
 
 
 
  1. // Objects/obmalloc.c 
  2. struct arena_object { 
  3.     // arena_object地址 
  4.     uintptr_t address; 
  5.     // 將arena的地址用于給pool使用而對齊的地址 
  6.     block* pool_address; 
  7.     // 該arena中可用pool的數(shù)量 
  8.     uint nfreepools;  
  9.     // 該arena中所有pool的數(shù)量 
  10.     uint ntotalpools;
  11.       //  使用完畢的pool,用單鏈表維護 
  12.     struct pool_header* freepools; 
  13.     // 雙向鏈表指針 
  14.     struct arena_object* nextarena; 
  15.     struct arena_object* prevarena; 
  16. };

為什么arena_object需要address和pool_address2個字段?

    “    上面內(nèi)存管理的劃分提到arena_object與body是不連續(xù)的,圖4

pool_header被申請時,它所管理的block集合的內(nèi)存一定也被申請了;所以他是連續(xù)的一塊空間

但是當aerna_object被申請時,它所管理的pool集合的內(nèi)存則沒有被申請;arena需要指針相連

所以address指定的是頭數(shù)據(jù),pool_address指定的是真實數(shù)據(jù)開始的位置,所以不同

new_arena

類型

    “    uintptr_t 是由從 C99 開始導入的 stdint.h 提供的,在將 C 指針轉(zhuǎn)化成整數(shù)時,它起著很大的作用。uintptr_t 正是負責填補這種環(huán)境差異的。uintptr_t 會根據(jù)環(huán)境變換成 4 字節(jié)或 8 字節(jié),將指針安全地轉(zhuǎn)化,避免發(fā)生溢出的問題。

 
 
 
 
  1. // uchar 和 uint 分別是 unsigned ××× 的略稱。 
  2. #undef uchar 
  3. #define uchar unsigned char /* 約8位 */ 
  4. #undef uint 
  5. #define uint unsigned int /* 約大于等于16位 */ 
  6. #undef ulong 
  7. #define ulong unsigned long /* 約大于等于32位 */ 
  8. #undef uptr 
  9. #define uptr Py_uintptr_t 
  10. typedef uchar block; 
 
 
 
 
  1. //[obmalloc.c] 
  2. // arenas管理著arena_object的集合 
  3. static struct arena_object* arenas = NULL; 
  4. // 當前arenas中管理的arena_object的個數(shù) 
  5. static uint maxarenas = 0; 
  6. // “未使用的”arena_objectd單向鏈表 
  7. static struct arena_object* unused_arena_objects = NULL; 
  8. // “可用的”arena_object鏈表 
  9. static struct arena_object* usable_arenas = NULL; 
  10. // 初始化時需要申請的arena_object的個數(shù)
  11. #define INITIAL_ARENA_OBJECTS 16 
 
 
 
 
  1. //[obmalloc.c] 
  2. static struct arena_object*  
  3. new_arena(void) 
  4.     struct arena_object* arenaobj; 
  5.     uint excess;  /* number of bytes above pool alignment */   
  6.   // 初始化默認值為NULL,需要生成arena_objects  
  7.     if (unused_arena_objects == NULL) { 
  8.       uint i; 
  9.       uint numarenas; 
  10.       size_t nbytes; 
  11.       // 確定申請arena的個數(shù),初始化得到16個,之后會2倍擴容 
  12.       numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS;    
  13.        // 溢出判斷 
  14.       if (numarenas <= maxarenas) 
  15.         return NULL;   
  16.       nbytes = numarenas * sizeof(*arenas); 
  17.       if (nbytes / sizeof(*arenas) != numarenas) 
  18.         return NULL;    
  19.        // 需要使用0層的分配器分配numarenas個數(shù)arena_object(頭信息)所需的raw memory
  20.        // 分配完后arenas作為靜態(tài)全局變量 
  21.       arenaobj = (struct arena_object *)realloc(arenas, nbytes); 
  22.       if (arenaobj == NULL) 
  23.         return NULL; 
  24.       arenas = arenaobj;
  25.        // 把以上分配的raw memory,維護到unused_arena_objects單向鏈表中 
  26.       for (i = maxarenas; i < numarenas; ++i) { 
  27.         // arena地址,如果沒有分配就用0作為標識符 
  28.         arenas[i].address = 0;  
  29.         // 最后一個arena指向NULL,其余都指向下一個指針,初始化分配是一個連續(xù)的單鏈表 
  30.         arenas[i].nextarena = i < numarenas - 1 ? &arenas[i+1] : NULL; 
  31.       } 
  32.       /* 反映到全局變量中 */ 
  33.       unused_arena_objects = &arenas[maxarenas]; 
  34.       maxarenas = numarenas; 
  35.     }  
  36. ////////////////////////////////////以上完成了arenas 的初始化,如下圖所示////////////////////////////////////////// 
  37.      // 從unused_arena_objects鏈表中取出一個“未使用的”arena_object(表頭) 
  38.     arenaobj = unused_arena_objects; 
  39.     unused_arena_objects = arenaobj->nextarena; 
  40.     assert(arenaobj->address == 0); 
  41.      // 分配一塊arena內(nèi)存,256KB 
  42.     // 這時候address有具體地址了 
  43.     arenaobj->address = (uptr)malloc(ARENA_SIZE); 
  44.     ++narenas_currently_allocated; 
  45.      if (arenaobj->address == 0) { 
  46.     // 分配失敗,讓把拿出來的頭放回到unused_arena_objects鏈表中 
  47.         arenaobj->nextarena = unused_arena_objects; 
  48.         unused_arena_objects = arenaobj; 
  49.         return NULL; 
  50.     }  
  51.  ///////////////////////////////以上是分配arena空間與arena_object連接///////////////////////////////////////    
  52.      // 將arena內(nèi)的空間分割為各個pool 
  53.     arenaobj->freepools = NULL; 
  54.    /* pool_address  對齊后開頭pool的地址 
  55.      nfreepools  對齊后arena中pool的數(shù)量 */ 
  56.     arenaobj->pool_address = (block*)arenaobj->address; 
  57.     arenaobj->nfreepools = ARENA_SIZE / POOL_SIZE;   
  58.     // 內(nèi)存對齊 
  59.     excess = (uint)(arenaobj->address & POOL_SIZE_MASK); 
  60.     if (excess != 0) {
  61.        --arenaobj->nfreepools; 
  62.       arenaobj->pool_address += POOL_SIZE - excess; 
  63.    } 
  64.     arenaobj->ntotalpools = arenaobj->nfreepools;
  65.      return arenaobj; 
  66. /////////////////////////////////////////以上是劃分pool/////////////////////////////////////////////////////

1、初始化16個arena_object

圖5

2、擴容

圖6

3、分配arena空間,就是arena表頭與真實數(shù)據(jù)相連

圖7

4、給arena劃分pool,excess是什么-內(nèi)存對齊會消耗一個pool

結(jié)構(gòu)體 arena_object 的成員 pool_address 中存有以 4K 字節(jié)對齊的 pool 的地址。

在此使用 POOL_SIZE_MASK 來對用 malloc() 保留的 arena 的地址進行屏蔽處理,計算超過的量(excess)。

如果超過的量(excess)為 0,因為 arena 的地址剛好是 4K 字節(jié)(2 的 12 次方)的倍數(shù),所以程序會原樣返回分配的 arena_object。這時候因為 arena 內(nèi)已經(jīng)被 pool 填滿了,所以可以通過計算 arena 的大小或 pool 的大小來求出 arena 內(nèi) pool 的數(shù)量。

如果超過的量不為 0,程序就會計算“arena 的地址 + 超過的量”,將其設置為成員pool_address。此時 arena 內(nèi)前后加起來會產(chǎn)生一個 pool 的空白,nfreepools--。

圖8

arena的2個狀態(tài)

    “    arena_object是否與pool建立聯(lián)系導致狀態(tài)不同

unused_arena_object(未使用狀態(tài))

  •  只有當結(jié)構(gòu)體arena_object的成員address為0時,才將其存入這個列表
    •   剛剛new_arena()產(chǎn)生的arena_object,還沒和pool建立連接
    •   在PyObject_Free()時arena為空的情況下,arena_object會頭插于此鏈表
  •  單向鏈表維護

usable_arenas(可用狀態(tài))

  •  有已經(jīng)使用過的pool和還未被使用的都是empty狀態(tài),也就是nfreepool>0
    •  used狀態(tài)都是被usedpools管轄起來了,當全是used狀態(tài)的arena哪怕pool還有可能用的塊,也是要從此雙鏈表中刪除。因為申請內(nèi)存的時候會去usedpool找的。所以只需要判斷usable_arenas->nfreepools == 0,從雙鏈表中刪除
  •  雙向鏈表維護
    •   鏈表按照block數(shù)量最多的arena的順序排列。(基于成員nfreepools升序排列,意思就是先盡量用完整個arena)

圖9

Python對象分配器(2層)

    “    第 2 層的分配器負責管理 pool 內(nèi)的 block。這一層實際上是將 block 的開頭地址返回給申請者,并釋放 block 等。

block

一個pool被分割成一個個的block。Python中生成對象時,最終都會被分一個或幾個block上。block是Python內(nèi)存分配的最小單元

內(nèi)存對齊

大小以8個字節(jié)為梯度的內(nèi)存塊,就是類保證內(nèi)存對齊(字對齊)

1、提高了CPU的讀寫速度

2、減少了碎片大?。ū夭豢缮俚睦速M)

 
 
 
 
  1. // 以下的宏 
  2. // 索引為0的話, 就是1 << 3, 顯然結(jié)果為8 
  3. // 索引為1的話, 就是2 << 3, 顯然結(jié)果為16 
  4. #define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT)  
  5.  * Request in bytes     Size of allocated block      Size class idx 
  6.  * ---------------------------------------------------------------- 
  7.  *        1-8                     8                       0 
  8.  *        9-16                   16                       1 
  9.  *       17-24                   24                       2 
  10.  *       25-32                   32                       3 
  11.  *       33-40                   40                       4 
  12.  *       41-48                   48                       5 
  13.  *       49-56                   56                       6 
  14.  *       57-64                   64                       7 
  15.  *       65-72                   72                       8 
  16.  *        ...                   ...                     ... 
  17.  *      497-504                 504                      62 
  18.  *      505-512                 512                      63

所以當我們需要申請44個字節(jié)的內(nèi)存空間的時候,PyObject_Malloc會從內(nèi)存池中劃分一個 48 字節(jié)的block使用

 
 
 
 
  1. //Objects/obmalloc.c 
  2. #define ALIGNMENT               8               /* must be 2^N */ 
  3. #define ALIGNMENT_SHIFT         3

    “    我們可以從圖8里看到excess是為了在arena中pool4K大小的對齊,所以block以8字節(jié)的倍數(shù)自然都是對齊的

由于pool_header中szidx確定

圖10

利用內(nèi)存對齊的hack

CPU 原則上能從對齊的地址取出數(shù)據(jù)。相應地,malloc() 分配的地址也應配合 CPU 對齊來返回數(shù)據(jù)。

利用這一點的著名 hack 就是將地址的低 3 位用作標志。

假設在結(jié)構(gòu)體內(nèi)存入某個指針。如果從 malloc() 返回的地址是按 8 字節(jié)對齊的,那么其指針的低 3 位肯定為“0”。于是我們想到了在這里設置位,將其作為標志來使用。當我們真的要訪問這個指針時,就將低 3 位設為 0,無視標志。

這是一個非常大膽的 hack,但事實上 glibc malloc 卻實現(xiàn)了這個 hack。

block的狀態(tài)

block 有3種狀態(tài)管理

  •  已經(jīng)分配
  •  使用完畢:就是已經(jīng)被使用過,再次釋放的block
    •  freeblock單向鏈表維護使用完畢的塊,block是在發(fā)生釋放的時候連接到鏈表上的
    •  freeblock是指向第一塊空閑可以使用的塊,當還沒有產(chǎn)生使用完畢的塊時候,他是NULL。那么一直是通過nextoffset來使用未使用的塊,當有回收的塊那么freeblock就指向第一個空閑的塊,并優(yōu)先與偏移量nextoffset使用。

    未使用:未使用自然沒有鏈表的指向了,那么我們只能在pool_head上設置第一個可以使用塊的偏移量nextoffset

圖11

pool

pool的大小

pool是與系統(tǒng)頁一樣的4KB的大小,其中一個pool只能管理一個種規(guī)格的block,由szidx字段來標識。所以pool是具有size概念的block集合

 
 
 
 
  1. //Objects/obmalloc.c 
  2. #define SYSTEM_PAGE_SIZE        (4 * 1024) 
  3. #define SYSTEM_PAGE_SIZE_MASK   (SYSTEM_PAGE_SIZE - 1) 
  4. #define POOL_SIZE               SYSTEM_PAGE_SIZE        /* must be 2^N */ 
  5. #define POOL_SIZE_MASK          SYSTEM_PAGE_SIZE_MASK

pool的內(nèi)存對齊

在講解arena初始化的時候第4部分講到了excess就是為了做pool的內(nèi)存對齊,可見圖8。這里就不在贅述

pool的頭結(jié)構(gòu)

一個pool的頭由48個字節(jié)組成,所有的pool以雙向鏈表的形式連接

 
 
 
 
  1. //Objects/obmalloc.c 
  2. /* When you say memory, my mind reasons in terms of (pointers to) blocks */ 
  3. typedef uint8_t block; 
  4. /* Pool for small blocks. */ 
  5. struct pool_header { 
  6.     union { block *_padding; 
  7.             uint count; } ref;          /* 當前pool里面已分配出去的block數(shù)量    */ 
  8.     block *freeblock;                   /* 指向空閑block鏈表的第一塊          */ 
  9.     struct pool_header *nextpool;       /* next和prev提供usedpool使用,減少緩存表的空間  */ 
  10.     struct pool_header *prevpool;      
  11.     uint arenaindex;                    /* 自己所屬的arena的索引(對于arenas而言)          */ 
  12.     uint szidx;                         /* 分配的block的大小,所以pool中的所有塊大小一致 */ 
  13.     uint nextoffset;                    /* 下一個可用block的內(nèi)存偏移量        */ 
  14.     uint maxnextoffset;                 /* 最后一個block距離開始位置的偏移量     */ 
  15. }; 
  16. typedef struct pool_header *poolp;

圖12

pool的狀態(tài)

  •  empty狀態(tài):pool中所有的block都未被使用
  •  已經(jīng)使用完的,pool已經(jīng)有pool_size,意味著大小已經(jīng)確定的pool
  •  used狀態(tài):pool中至少有一個block已經(jīng)被使用,并且至少有一個block未被使用。由usedpools數(shù)組維護
  •  full狀態(tài):pool中所有的block都已經(jīng)被使用,并從usedpools鏈表上刪除。

圖13

usedpools

    “    作用就是管理所有used狀態(tài)的pool

 
 
 
 
  1. // poolp大概是pool_header的指針型的別名。也就是說,usedpools 是 pool_header 的指針型的數(shù)組。 
  2. typedef struct pool_header *poolp;

宏 NB_SMALL_SIZE_CLASSES

 
 
 
 
  1. #define ALIGNMENT 8 /* 有必要為2的N次方 */ 
  2. #define SMALL_REQUEST_THRESHOLD 512 
  3. // 指明了在當前的配置之下,一共有多少個size class。 
  4. #define NB_SMALL_SIZE_CLASSES   (SMALL_REQUEST_THRESHOLD / ALIGNMENT)

usedpools的初始化大小

 
 
 
 
  1. // 這個宏定義了一個指針,這個指針指向的位置是從一組的開頭再往前“兩個 block 指針型的大小”。 
  2. #define PTA(x)  ((poolp )((uint8_t *)&(usedpools[2*(x)]) - 2*sizeof(block *))) 
  3. // 宏 PT() 以兩個一組的形式調(diào)用宏 PTA()。 
  4. #define PT(x)   PTA(x), PTA(x) 
  5. // usedpools數(shù)組有128個 
  6. static poolp usedpools[2 * ((NB_SMALL_SIZE_CLASSES + 7) / 8) * 8] = { 
  7.     PT(0), PT(1), PT(2), PT(3), PT(4), PT(5), PT(6), PT(7) 
  8. #if NB_SMALL_SIZE_CLASSES > 8 
  9.     , PT(8), PT(9), PT(10), PT(11), PT(12), PT(13), PT(14), PT(15) 
  10. #if NB_SMALL_SIZE_CLASSES > 16 
  11.     , PT(16), PT(17), PT(18), PT(19), PT(20), PT(21), PT(22), PT(23) 
  12. #if NB_SMALL_SIZE_CLASSES > 24 
  13.     , PT(24), PT(25), PT(26), PT(27), PT(28), PT(29), PT(30), PT(31) 
  14. #if NB_SMALL_SIZE_CLASSES > 32 
  15.     , PT(32), PT(33), PT(34), PT(35), PT(36), PT(37), PT(38), PT(39) 
  16. #if NB_SMALL_SIZE_CLASSES > 40 
  17.     , PT(40), PT(41), PT(42), PT(43), PT(44), PT(45), PT(46), PT(47) 
  18. #if NB_SMALL_SIZE_CLASSES > 48 
  19.     , PT(48), PT(49), PT(50), PT(51), PT(52), PT(53), PT(54), PT(55) 
  20. #if NB_SMALL_SIZE_CLASSES > 56 
  21.     , PT(56), PT(57), PT(58), PT(59), PT(60), PT(61), PT(62), PT(63) 
  22. #if NB_SMALL_SIZE_CLASSES > 64 
  23. #error "NB_SMALL_SIZE_CLASSES should be less than 64" 
  24. #endif /* NB_SMALL_SIZE_CLASSES > 64 */
  25. #endif /* NB_SMALL_SIZE_CLASSES > 56 */ 
  26. #endif /* NB_SMALL_SIZE_CLASSES > 48 */ 
  27. #endif /* NB_SMALL_SIZE_CLASSES > 40 */ 
  28. #endif /* NB_SMALL_SIZE_CLASSES > 32 */ 
  29. #endif /* NB_SMALL_SIZE_CLASSES > 24 */ 
  30. #endif /* NB_SMALL_SIZE_CLASSES > 16 */ 
  31. #endif /* NB_SMALL_SIZE_CLASSES >  8 */ 
  32. };

現(xiàn)在以為usedpool的角度出發(fā)來看

圖14

usedpools如何做的快-像hash一樣處理

used就是把使用了至少一個塊,但是還沒有全部使用完的pool整合到一個usedpool中,那么這一個做法類似以hash表的鏈地址法,通過下標可以O(1)到達同一size的usedpool[下標]的位置,然后使用鏈表,因為empty->used和used->full,方便插入和刪除pool

一個例子

1、當申請20個字節(jié)內(nèi)存的時候,Python會首先獲得size class index,通過size = (uint )(nbytes \- 1) >> ALIGNMENT_SHIFT,其中ALIGNMENT_SHIFT是內(nèi)存對齊的需要右移3位(即8字節(jié)對齊),得到(20-1)>>3=2

2、通過usedpools[i+i]->nextpool可以快速找到一個最合適當前內(nèi)存需求的pool

 
 
 
 
  1. byte = 20 /* 申請的字節(jié)數(shù)*/ 
  2. byte = (20 - 1) >> 3 /* 對齊:結(jié)果 2 */ 
  3. pool = usedpools[byte+byte] /* 因為是兩兩一組,所以索引加倍: index 4 */    // O(1) 
  4. // 這時,取出的 pool 存在如下關系。
  5. pool; == pool->nextpool 
  6. pool; == pool->prevpool 
  7. pool->nextpool == pool->prevpool         // O(1)

usedpool也需要盡可能節(jié)省空間

在需要緩存的時候,能夠盡可能地讓緩存少承載一些引用表。(只需要pool_header中兩個內(nèi)部的指針成員,next和prev)

如果直接保留 pool_header 的話,往往就會出現(xiàn) usedpools 變得太大,緩存承載不下的狀況。因為我們要頻繁引用數(shù)組 usedpools,所以讓它小一些才會減輕緩存的壓力。

arena和pool的釋放策略

通過盡量不使用那些可用空間多的內(nèi)存空間,增加了使其完全變?yōu)榭盏臋C會。如果這部分內(nèi)存空間完全為空,那么就能將其釋放。

  •  usable_arenas:是按照nfreepools升序排序的,目的是為了盡可能先使用完一個arena
  •  當full->used狀態(tài):都是頭插到usedpools中的,也是為了現(xiàn)使用完一個pool

為什么usedpools需要2倍的空間

在釋放的時候從pymalloc_free函數(shù)觀察來看,是頭插放在usedpool[奇數(shù)],full狀態(tài)變?yōu)閡sed狀態(tài)

 
 
 
 
  1. // free中的代碼, 
  2.   if (UNLIKELY(lastfree == NULL)) { 
  3.   uint size = pool->szidx; 
  4.       poolp next = usedpools[size + size];   // 雙向鏈表的尾部 
  5.       poolp prev = next->prevpool; 
  6.       pool->nextnextpool = next; 
  7.       pool->prevprevpool = prev;
  8.       next->prevpool = pool;  <
    網(wǎng)站題目:Python內(nèi)存管理介紹
    URL標題:http://www.dlmjj.cn/article/cdiggjs.html