Python内存管理介绍

CPython内存管理器

CPython源码包的功能分类

此文是按照源码Python3.9来写,其中有些assert语句与一些不必要的宏字段会删除,保留核心的逻辑并添加注释,方便自己和大家理解。在代码中都会注明源码出处方便大家完整阅读。

为哈巴河等地区用户提供了全套网页设计制作服务,及哈巴河网站建设行业解决方案。主营业务为成都网站建设、网站设计、哈巴河网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!

目录 概要
Demo 采用了Python的演示应用程序
Doc 文档
Grammer Python的语法文件
Include 编译Python时引用的各种头文件
Lib 标准附加库
Mac Mac用的工具等
Misc 很多文件的集合(如gdbinit和vimrc等)
Modules Python的C语言扩展模块
Objects Python的对象用的C语言代码
PC 依存于OS等环境的程序
PCbuild 构造Win32和x64时使用
Parser Python用的解析器
Python Python的核心

Python的内存管理架构

Python是一门动态的、一切皆对象的语言,这些内存申请可能会产生大量小的内存,为了加快内存操作和减少内存碎片化,使用Python自己的内存管理器,叫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 --> |    # 对象特有的内存分配器 
  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低级内存分配器 
  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特有的虚拟内存管理器 
  26.     __________________________________   __________________________________ 
  27.    [                                  ] [                                  ] 
  28. -2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> |  # 物理内存和交换目的地(如HDD等) 
  29. */ 
 
 
 
 
  1. PyDict_New()               // 第三层 
  2.  PyObject_GC_New()     // 第二层 
  3.   PyObject_Malloc()     // 第二层 
  4.    new_arena()       // 第一层 
  5.    malloc()        // 第零层 
  6. ////////////////////////////////////////以下2层属于操作系统范畴,不在讨论范围/////////////////////////////////

图1

通用的基础分配器(0层)

512字节是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的分配器,函数进去就会有判断(0,512]的才使用 
  9.     void* ptr = pymalloc_alloc(ctx, nbytes); 
  10.     if (LIKELY(ptr != NULL)) { 
  11.         return ptr; 
  12.     } 
  13.   // 大于512字节走C的malloc,函数进去进做了越界判断,Py_ssize_t为阈值 
  14.     ptr = PyMem_RawMalloc(nbytes); 
  15.     if (ptr != NULL) { 
  16.         raw_allocated_blocks++; 
  17.     } 
  18.     return ptr; 
  19. }
  •  0: 直接调用 malloc 函数
  •  1 ~ 512: 由Python的内存池负责分配,内存池以内存尺寸进行划分
  •  512以上: 直接调动 malloc 函数

在源代码中以PyMem_为前缀的所有函数是封装C语言提供给Python语法使用的,其核心使用的就是第0层malloc之类的C库函数。

通常Python没有对小块内存的内存池的大小做任何的限制

当Python在WITH_MEMORY_LIMITS编译符号打开的背景下进行编译时,Python内部的另一个符号会被激活,这个名为SMALL_MEMORY_LIMIT的符号限制了整个内存池的大小,同时,也就限制了可以创建的arena的个数。

在默认情况下,不论是Win32平台,还是unix平台,这个编译符号都是没有打开的,所以通常Python都没有对小块内存的内存池的大小做任何的限制。

 
 
 
 
  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让我们只需要提供类型和数量

有了以下的宏定义,我们写代码的时候只需要提供类型和数量,而不用自己去计算具体需要申请多少空间

 
 
 
 
  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

内存碎片问题

每次申请内存的时候一定不会每次都遇到刚好的块去分配,那么一下一大块内存会被切割使用,那么中间会产生很多小的但是可能不在会被使用的碎片(但是整个加起来也是一个大的可使用的块),而且每次查找合适的块需要遍历整个堆,所以为了减少碎片和快速分配内存,我们需要内存管理。

图2

Python内存管理的划分

小于512字节的内存申请由Python的低级分配器接管(空白内存,raw memory),做了3级层次的划分,依次为block、pool、arena

  •  block是Python内存管理的最小单元,其中他的大小与pool_head的szidx一致,而且采用的Best-fit分配策略
    •   Best-fit分配策略:返回大于等于 size 的最小分块
  •  pool是管理一类规格的block,是具有size概念的内存管理抽象体,有pool_head的一个szidx管理。(当然她还有状态的管理后面会介绍)
  •  arena是可以管理多个pool,每个pool的规格可以各不相同。(他也有自己的状态管理后面会介绍)

图3

pool与arena头与boby连接的不同

图4

Python低级内存分配器(1层)

现在来到的是真正Python的内存管理谈论的部分了,Python内存管理做了哪些处理

  •  减少内存碎片的问题
    •   上面的block概念的提出,是为了有效改善内存碎片的问题,但是不可能解决的
  • 不可能让每次分配都遍历整个堆
    •   所以arena_head、pool_head都比较复杂,其中都维护了多条链表来把开销从O(N)降低到O(1)
  •  Python分配器主要是处理<512字节小内存,频繁的分配/释放一定是会浪费
    •   Python的大部分基础类引入了缓存池的机制用于管理小块内存的申请和释放,提供pymalloc_alloc、pymalloc_realloc、pymalloc_free三个接口
      •   比如字典有80大小的数组作为缓存池
      •   列表也有80大小的数组作为缓存池

arena

    “    第一层的核心就是创建arena

arena的大小

arena的默认值是256K

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

arena头结构体 

 
 
 
 
  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的数量 
  8.     uint nfreepools;  
  9.     // 该arena中所有pool的数量 
  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个字段?

    “    上面内存管理的划分提到arena_object与body是不连续的,图4

pool_header被申请时,它所管理的block集合的内存一定也被申请了;所以他是连续的一块空间

但是当aerna_object被申请时,它所管理的pool集合的内存则没有被申请;arena需要指针相连

所以address指定的是头数据,pool_address指定的是真实数据开始的位置,所以不同

new_arena

类型

    “    uintptr_t 是由从 C99 开始导入的 stdint.h 提供的,在将 C 指针转化成整数时,它起着很大的作用。uintptr_t 正是负责填补这种环境差异的。uintptr_t 会根据环境变换成 4 字节或 8 字节,将指针安全地转化,避免发生溢出的问题。

 
 
 
 
  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的个数 
  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的个数
  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的个数,初始化得到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个数arena_object(头信息)所需的raw memory
  20.        // 分配完后arenas作为静态全局变量 
  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,其余都指向下一个指针,初始化分配是一个连续的单链表 
  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内存,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内的空间分割为各个pool 
  53.     arenaobj->freepools = NULL; 
  54.    /* pool_address  对齐后开头pool的地址 
  55.      nfreepools  对齐后arena中pool的数量 */ 
  56.     arenaobj->pool_address = (block*)arenaobj->address; 
  57.     arenaobj->nfreepools = ARENA_SIZE / POOL_SIZE;   
  58.     // 内存对齐 
  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表头与真实数据相连

图7

4、给arena划分pool,excess是什么-内存对齐会消耗一个pool

结构体 arena_object 的成员 pool_address 中存有以 4K 字节对齐的 pool 的地址。

在此使用 POOL_SIZE_MASK 来对用 malloc() 保留的 arena 的地址进行屏蔽处理,计算超过的量(excess)。

如果超过的量(excess)为 0,因为 arena 的地址刚好是 4K 字节(2 的 12 次方)的倍数,所以程序会原样返回分配的 arena_object。这时候因为 arena 内已经被 pool 填满了,所以可以通过计算 arena 的大小或 pool 的大小来求出 arena 内 pool 的数量。

如果超过的量不为 0,程序就会计算“arena 的地址 + 超过的量”,将其设置为成员pool_address。此时 arena 内前后加起来会产生一个 pool 的空白,nfreepools--。

图8

arena的2个状态

    “    arena_object是否与pool建立联系导致状态不同

unused_arena_object(未使用状态)

  •  只有当结构体arena_object的成员address为0时,才将其存入这个列表
    •   刚刚new_arena()产生的arena_object,还没和pool建立连接
    •   在PyObject_Free()时arena为空的情况下,arena_object会头插于此链表
  •  单向链表维护

usable_arenas(可用状态)

  •  有已经使用过的pool和还未被使用的都是empty状态,也就是nfreepool>0
    •  used状态都是被usedpools管辖起来了,当全是used状态的arena哪怕pool还有可能用的块,也是要从此双链表中删除。因为申请内存的时候会去usedpool找的。所以只需要判断usable_arenas->nfreepools == 0,从双链表中删除
  •  双向链表维护
    •   链表按照block数量最多的arena的顺序排列。(基于成员nfreepools升序排列,意思就是先尽量用完整个arena)

图9

Python对象分配器(2层)

    “    第 2 层的分配器负责管理 pool 内的 block。这一层实际上是将 block 的开头地址返回给申请者,并释放 block 等。

block

一个pool被分割成一个个的block。Python中生成对象时,最终都会被分一个或几个block上。block是Python内存分配的最小单元

内存对齐

大小以8个字节为梯度的内存块,就是类保证内存对齐(字对齐)

1、提高了CPU的读写速度

2、减少了碎片大小(必不可少的浪费)

 
 
 
 
  1. // 以下的宏 
  2. // 索引为0的话, 就是1 << 3, 显然结果为8 
  3. // 索引为1的话, 就是2 << 3, 显然结果为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个字节的内存空间的时候,PyObject_Malloc会从内存池中划分一个 48 字节的block使用

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

    “    我们可以从图8里看到excess是为了在arena中pool4K大小的对齐,所以block以8字节的倍数自然都是对齐的

由于pool_header中szidx确定

图10

利用内存对齐的hack

CPU 原则上能从对齐的地址取出数据。相应地,malloc() 分配的地址也应配合 CPU 对齐来返回数据。

利用这一点的著名 hack 就是将地址的低 3 位用作标志。

假设在结构体内存入某个指针。如果从 malloc() 返回的地址是按 8 字节对齐的,那么其指针的低 3 位肯定为“0”。于是我们想到了在这里设置位,将其作为标志来使用。当我们真的要访问这个指针时,就将低 3 位设为 0,无视标志。

这是一个非常大胆的 hack,但事实上 glibc malloc 却实现了这个 hack。

block的状态

block 有3种状态管理

  •  已经分配
  •  使用完毕:就是已经被使用过,再次释放的block
    •  freeblock单向链表维护使用完毕的块,block是在发生释放的时候连接到链表上的
    •  freeblock是指向第一块空闲可以使用的块,当还没有产生使用完毕的块时候,他是NULL。那么一直是通过nextoffset来使用未使用的块,当有回收的块那么freeblock就指向第一个空闲的块,并优先与偏移量nextoffset使用。

    未使用:未使用自然没有链表的指向了,那么我们只能在pool_head上设置第一个可以使用块的偏移量nextoffset

图11

pool

pool的大小

pool是与系统页一样的4KB的大小,其中一个pool只能管理一个种规格的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的内存对齐

在讲解arena初始化的时候第4部分讲到了excess就是为了做pool的内存对齐,可见图8。这里就不在赘述

pool的头结构

一个pool的头由48个字节组成,所有的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数量    */ 
  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的内存偏移量        */ 
  14.     uint maxnextoffset;                 /* 最后一个block距离开始位置的偏移量     */ 
  15. }; 
  16. typedef struct pool_header *poolp;

图12

pool的状态

  •  empty状态:pool中所有的block都未被使用
  •  已经使用完的,pool已经有pool_size,意味着大小已经确定的pool
  •  used状态:pool中至少有一个block已经被使用,并且至少有一个block未被使用。由usedpools数组维护
  •  full状态:pool中所有的block都已经被使用,并从usedpools链表上删除。

图13

usedpools

    “    作用就是管理所有used状态的pool

 
 
 
 
  1. // poolp大概是pool_header的指针型的别名。也就是说,usedpools 是 pool_header 的指针型的数组。 
  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() 以两个一组的形式调用宏 PTA()。 
  4. #define PT(x)   PTA(x), PTA(x) 
  5. // usedpools数组有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. };

现在以为usedpool的角度出发来看

图14

usedpools如何做的快-像hash一样处理

used就是把使用了至少一个块,但是还没有全部使用完的pool整合到一个usedpool中,那么这一个做法类似以hash表的链地址法,通过下标可以O(1)到达同一size的usedpool[下标]的位置,然后使用链表,因为empty->used和used->full,方便插入和删除pool

一个例子

1、当申请20个字节内存的时候,Python会首先获得size class index,通过size = (uint )(nbytes \- 1) >> ALIGNMENT_SHIFT,其中ALIGNMENT_SHIFT是内存对齐的需要右移3位(即8字节对齐),得到(20-1)>>3=2

2、通过usedpools[i+i]->nextpool可以快速找到一个最合适当前内存需求的pool

 
 
 
 
  1. byte = 20 /* 申请的字节数*/ 
  2. byte = (20 - 1) >> 3 /* 对齐:结果 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也需要尽可能节省空间

在需要缓存的时候,能够尽可能地让缓存少承载一些引用表。(只需要pool_header中两个内部的指针成员,next和prev)

如果直接保留 pool_header 的话,往往就会出现 usedpools 变得太大,缓存承载不下的状况。因为我们要频繁引用数组 usedpools,所以让它小一些才会减轻缓存的压力。

arena和pool的释放策略

通过尽量不使用那些可用空间多的内存空间,增加了使其完全变为空的机会。如果这部分内存空间完全为空,那么就能将其释放。

  •  usable_arenas:是按照nfreepools升序排序的,目的是为了尽可能先使用完一个arena
  •  当full->used状态:都是头插到usedpools中的,也是为了现使用完一个pool

为什么usedpools需要2倍的空间

在释放的时候从pymalloc_free函数观察来看,是头插放在usedpool[奇数],full状态变为used状态

 
 
 
 
  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;  <

    本文题目:Python内存管理介绍
    网页路径:http://www.shufengxianlan.com/qtweb/news17/83817.html

    网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

    广告

    声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联