文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Python内存管理介绍

2024-12-03 05:25

关注
目录 概要
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.   
  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.   
  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;  

在源代码中以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)    
  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

图3

pool与arena头与boby连接的不同

图4

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

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

arena

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

arena的大小

arena的默认值是256K 

  1. #define ARENA_BITS              18                      
  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   
  4. #undef uint  
  5. #define uint unsigned int   
  6. #undef ulong  
  7. #define ulong unsigned long   
  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.  
  5.     struct arena_object* arenaobj;  
  6.     uint excess;      
  7.   // 初始化默认值为NULL,需要生成arena_objects   
  8.     if (unused_arena_objects == NULL) {  
  9.       uint i;  
  10.       uint numarenas;  
  11.       size_t nbytes;  
  12.       // 确定申请arena的个数,初始化得到16个,之后会2倍扩容  
  13.       numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS;     
  14.        // 溢出判断  
  15.       if (numarenas <= maxarenas)  
  16.         return NULL;    
  17.       nbytes = numarenas * sizeof(*arenas);  
  18.       if (nbytes / sizeof(*arenas) != numarenas)  
  19.         return NULL;     
  20.        // 需要使用0层的分配器分配numarenas个数arena_object(头信息)所需的raw memory 
  21.        // 分配完后arenas作为静态全局变量  
  22.       arenaobj = (struct arena_object *)realloc(arenas, nbytes);  
  23.       if (arenaobj == NULL)  
  24.         return NULL;  
  25.       arenas = arenaobj
  26.        // 把以上分配的raw memory,维护到unused_arena_objects单向链表中  
  27.       for (i = maxarenas; i < numarenas; ++i) {  
  28.         // arena地址,如果没有分配就用0作为标识符  
  29.         arenas[i].address = 0;   
  30.         // 最后一个arena指向NULL,其余都指向下一个指针,初始化分配是一个连续的单链表  
  31.         arenas[i].nextarena = i < numarenas - 1 ? &arenas[i+1] : NULL;  
  32.       }  
  33.         
  34.       unused_arena_objects = &arenas[maxarenas];  
  35.       maxarenas = numarenas 
  36.     }   
  37. ////////////////////////////////////以上完成了arenas 的初始化,如下图所示//////////////////////////////////////////  
  38.      // 从unused_arena_objects链表中取出一个“未使用的”arena_object(表头)  
  39.     arenaobj = unused_arena_objects 
  40.     unused_arena_objects = arenaobj->nextarena;  
  41.     assert(arenaobj->address == 0);  
  42.      // 分配一块arena内存,256KB  
  43.     // 这时候address有具体地址了  
  44.     arenaobj->address = (uptr)malloc(ARENA_SIZE);  
  45.     ++narenas_currently_allocated;  
  46.      if (arenaobj->address == 0) {  
  47.     // 分配失败,让把拿出来的头放回到unused_arena_objects链表中  
  48.         arenaobj->nextarena = unused_arena_objects 
  49.         unused_arena_objects = arenaobj 
  50.         return NULL;  
  51.     }   
  52.  ///////////////////////////////以上是分配arena空间与arena_object连接///////////////////////////////////////     
  53.      // 将arena内的空间分割为各个pool  
  54.     arenaobj->freepools = NULL 
  55.      
  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.  
  67. /////////////////////////////////////////以上是划分pool///////////////////////////////////////////////////// 

初始化16个arena_object

图5

扩容

图6

分配arena空间,就是arena表头与真实数据相连

图7

给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(未使用状态)

usable_arenas(可用状态)

图9

Python对象分配器(2层)

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

block

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

内存对齐

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

提高了CPU的读写速度

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

  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                 
  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种状态管理

    未使用:未使用自然没有链表的指向了,那么我们只能在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          
  5. #define POOL_SIZE_MASK          SYSTEM_PAGE_SIZE_MASK 

pool的内存对齐

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

pool的头结构

一个pool的头由48个字节组成,所有的pool以双向链表的形式连接 

  1. //Objects/obmalloc.c  
  2.   
  3. typedef uint8_t block;  
  4.   
  5. struct pool_header {  
  6.     union { block *_padding;  
  7.             uint count; } ref;            
  8.     block *freeblock;                     
  9.     struct pool_header *nextpool;         
  10.     struct pool_header *prevpool;       
  11.     uint arenaindex;                      
  12.     uint szidx;                           
  13.     uint nextoffset;                      
  14.     uint maxnextoffset;                   
  15. };  
  16. typedef struct pool_header *poolp; 

图12

pool的状态

图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. #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  
  25. #endif   
  26. #endif   
  27. #endif   
  28. #endif   
  29. #endif   
  30. #endif   
  31. #endif   
  32. }; 

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

图14

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

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

一个例子

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

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

  1. byte = 20   
  2. byte = (20 - 1) >> 3   
  3. pool = usedpools[byte+byte]     // 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的释放策略

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

为什么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;  
  9.       prev->nextpool = pool;  
  10.       return 1;  
  11.   } 

而分配的时候使用是直接从usedpools[偶数]也会就是尾部开始使用的,所以也尽可能用光一个pool的 

  1. // 定位在尾部,直接使用  
  2.    poolp pool = usedpools[size + size];  
  3.    block *bp;  
  4.    if (LIKELY(pool != pool->nextpool)) {  
  5.       // block使用数量++  
  6.        ++pool->ref.count;  
  7.        bp = pool->freeblock;      // freeblock指向第一块空闲块,直接使用  
  8.        assert(bp != NULL); 

分配执行流程

pymalloc_alloc

    “    当申请的内存小于512字节就来到这个函数了,他的主要功能是分配block、分配pool、分配arena 

  1. // 下标映射到size大小  
  2. #define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT 
  3. // 内存对齐的宏  
  4. #define POOL_OVERHEAD   _Py_SIZE_ROUND_UP(sizeof(struct pool_header), ALIGNMENT) 
  5. #define DUMMY_SIZE_IDX          0xffff    
  1. //Objects/obmalloc.c  
  2. static void*  
  3. pymalloc_alloc(void *ctx, size_t nbytes)  
  4.  
  5.     // 1、如果申请的内存>512和==0的情况走朋友python0层,交给C处理  
  6.     // 如下是Python来接管这个raw memory,当然raw memory也是由C创建的  
  7.    if (UNLIKELY(nbytes == 0)) {  
  8.         return NULL;  
  9.     }  
  10.     if (UNLIKELY(nbytes > SMALL_REQUEST_THRESHOLD)) {  
  11.         return NULL;  
  12.     } 
  13.     // 2、用size去计算usedpools数组中的位置,  
  14.     uint size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;  
  15.     poolp pool = usedpools[size + size];  
  16.     block *bp;  
  17.    // 如果usedpools中的双向链表有pool那么就分配  
  18.     if (LIKELY(pool != pool->nextpool)) {  
  19.        // block使用数量++   
  20.         ++pool->ref.count;  
  21.         bp = pool->freeblock;      // freeblock指向第一块空闲块,直接使用  
  22.         assert(bp != NULL); 
  23.         if (UNLIKELY((pool->freeblock = *(block **)bp) == NULL)) {  
  24.           // 如果freeblock是NULL,通过偏移量取未使用的block  
  25.           if (UNLIKELY(pool->nextoffset <= pool->maxnextoffset)) {  
  26.             pool->freeblock = (block*)pool + pool->nextoffset;  
  27.             // 用小标去还原size  
  28.             pool->nextoffset += INDEX2SIZE(size);  
  29.             *(block **)(pool->freeblock) = NULL;         
  30.             return;  
  31.           }   
  32.             
  33.           poolp next;  
  34.           next = pool->nextpool;  
  35.           poolpool = pool->prevpool;  
  36.           next->prevpool = pool;  
  37.           pool->nextnextpool = next;  
  38.     } 
  39.      // usedpools没有可用的pool,需要去申请  
  40.     else {  
  41.         bp = allocate_from_new_pool(size);  
  42.     }        
  43.       // 返回pool内的块  
  44.     return (void *)bp;  
  45.  

allocate_from_new_pool 

  1. #define ROUNDUP(x) (((x) + ALIGNMENT_MASK) & ~ALIGNMENT_MASK)  
  2. #define POOL_OVERHEAD ROUNDUP(sizeof(struct pool_header))  
  3. // 虚拟大值,是为了防止与freepool中的block匹配上,这个虚拟值是标记用来初始化空pool的  
  4. #define DUMMY_SIZE_IDX 0xffff  
  1. static void*  
  2. allocate_from_new_pool(uint size)  
  3.    // 0、首先会尝试去usable_arenas双向链表中拿,没有可用的arena时,就调用new_arena()  
  4.     // new_arena将arena_object设置到usable_arenas中,因为是第一个所以双向链表指针都置空  
  5.     if (usable_arenas == NULL) {  
  6.       usable_arenas = new_arena();  
  7.       usable_arenas->nextarena = usable_arenas->prevarena = NULL 
  8.     }  
  9.     poolp pool = usable_arenas->freepools;   
  10.    // 1、freepools链表存在,使用已经使用完毕的pool(szidx已经确定需要匹配)  
  11.   // 那么要从freepools中取出,放到usedpools中  
  12.     if (pool != NULL) {  
  13.         usable_arenas->freepools = pool->nextpool;  
  14.        --usable_arenas->nfreepools;  
  15.        // freepools用完了,那么使用下个usable_arenas,归还arena_object头  
  16.         if (UNLIKELY(usable_arenas->nfreepools == 0)) {  
  17.            usable_arenasusable_arenas = usable_arenas->nextarena;  
  18.             if (usable_arenas != NULL) {  
  19.                 usable_arenas->prevarena = NULL 
  20.             }  
  21.         }  
  22.     }  
  23.   // 2、freepools链表不存在,使用未使用的pool,那么需要初始化空白pool  
  24.     else {  
  25.         pool = (poolp)usable_arenas->pool_address;  
  26.         pool->arenaindex = (uint)(usable_arenas - arenas);  
  27.        // 设置虚拟值是为了防止与freepool中的block匹配上,这个虚拟值是标记用来初始化空pool的  
  28.         pool->szidx = DUMMY_SIZE_IDX 
  29.         usable_arenas->pool_address += POOL_SIZE;  
  30.         --usable_arenas->nfreepools;  
  31.        // 如果没有可用的pool了把arena_object头归还  
  32.         if (usable_arenas->nfreepools == 0) {  
  33.             usable_arenasusable_arenas = usable_arenas->nextarena;  
  34.         }  
  35.     }   
  36.     // 无论是情况1还是2都是要返回一块block后,此pool插入usedpools[下标]的双向链表中,并作为第一个pool  
  37.     block *bp;  
  38.     poolp next = usedpools[size + size];   
  39.     pool->nextnextpool = next;  
  40.     pool->prevpool = next 
  41.     next->nextpool = pool;  
  42.     next->prevpool = pool;  
  43.     pool->ref.count = 1;       
  44.   // 使用的是情况1,直接使用freepools(指向第一个已经使用完的pool)链表上的块  
  45.     if (pool->szidx == size) {  
  46.         bp = pool->freeblock;  
  47.         assert(bp != NULL);  
  48.         pool->freeblock = *(block **)bp;  
  49.         return bp;  
  50.     }  
  51.   // 使用的情况2,需要初始化pool header的空白pool  
  52.     pool->szidx = size 
  53.   // 一个宏, 将szidx转成内存块的大小, 比如: 0->8, 1->16, 63->512  
  54.     size = INDEX2SIZE(size);  
  55.   // 跳过用于pool_header的内存,并进行对齐  
  56.     bp = (block *)pool + POOL_OVERHEAD;  
  57.     pool->nextoffset = POOL_OVERHEAD + (size << 1);  
  58.     pool->maxnextoffset = POOL_SIZE - size;  
  59.     pool->freeblock = bp + size;  
  60.     *(block **)(pool->freeblock) = NULL;    // 有空闲链表头指向空  
  61.     return bp;  

释放执行流程

这个函数有三个作用,分别是“释放 block”“释放 pool”以及“释放 arena”。

pymalloc_free

从block搜索pool的技巧 

  1. #define SYSTEM_PAGE_SIZE (4 * 1024) 
  2. #define SYSTEM_PAGE_SIZE_MASK (SYSTEM_PAGE_SIZE - 1)  
  3. #define POOL_SIZE_MASK SYSTEM_PAGE_SIZE_MASK  
  4. // 基于地址P获得离P最近的pool的边界地址  
  5. #define POOL_ADDR(P) ((poolp)_Py_ALIGN_DOWN((P), POOL_SIZE)) //等价如下  
  6. #define POOL_ADDR(P) (P & 0xfffff000) 

pool 地址对齐是按 4K 字节对齐的。也就是说,只要从pool 内部某处 block 的地址开始用 0xfffff000 标记,肯定能取到 pool 的开头。

末尾3个0是16^3=4096,取前面几位就一定是4K的倍数

 

  1. //Objects/obmalloc.c  
  2. static inline int  
  3. pymalloc_free(void *ctx, void *p)  
  4.  
  5.     poolp pool = POOL_ADDR(p);  
  6.    // 负责检查用宏 POOL_ADDR() 获得的 pool 是否正确  
  7.     if (UNLIKELY(!address_in_range(p, pool))) {  
  8.         return 0;  
  9.     } 
  10.     // 把需要释放的p,头插到freeblock中  
  11.     block *lastfree = pool->freeblock;  
  12.     *(block **)p = lastfree 
  13.     pool->freeblock = (block *)p;  
  14.     pool->ref.count--;   
  15.   // full状态变为used状态,是头插到usedpools中  
  16.     if (UNLIKELY(lastfree == NULL)) {  
  17.     uint size = pool->szidx;  
  18.         poolp next = usedpools[size + size];  
  19.         poolp prev = next->prevpool;  
  20.         pool->nextnextpool = next;  
  21.         pool->prevprevpool = prev;  
  22.         next->prevpool = pool;  
  23.         prev->nextpool = pool;  
  24.         return 1; 
  25.      }  
  26.   // 还有可分配的block  
  27.     if (LIKELY(pool->ref.count != 0)) {  
  28.           
  29.         return 1;  
  30.     }  
  31.   // 如果释放是最后一块,从used状态变为empty,要加入freepool链表(这是最复杂的情况,走insert_to_freepool函数)  
  32.     insert_to_freepool(pool);  
  33.     return 1;  

insert_to_freepool

在Python2.4之前一直存在内存泄漏的问题,因为python2.4对arena是没有区分"未使用"和可用的2种状态,所以当pool都释放了内存,arena始终不会释放它维护的pool集合。

5之后对arena的处理实际上分为了4种情况

  1. static void  
  2. insert_to_freepool(poolp pool)  
  3.  
  4.    // 从usedpools中取出pool  
  5.     poolp next = pool->nextpool;  
  6.     poolp prev = pool->prevpool;  
  7.     next->prevprevpool = prev;  
  8.     prev->nextnextpool = next;  
  9.    // 将pool头插到arena中的freepools中  
  10.     struct arena_object *ao = &arenas[pool->arenaindex];  
  11.     pool->nextpool = ao->freepools;  
  12.     ao->freepools = pool;  
  13.     uint nf = ao->nfreepools; 
  14.     struct arena_object* lastnf = nfp2lasta[nf]; 
  15.     if (lastnf == ao) {    
  16.         struct arena_object* p = ao->prevarena;  
  17.         nfp2lasta[nf] = (p != NULL && p->nfreepools == nf) ? p : NULL; 
  18.     } 
  19.      ao->nfreepools = ++nf;  
  20.     if (nf == ao->ntotalpools && ao->nextarena != NULL) {  
  21.           
  22.      // 从usable_arenas取出arena_object  
  23.         if (ao->prevarena == NULL) {  
  24.             usable_arenas = ao->nextarena;  
  25.         }  
  26.         else {  
  27.             ao->prevarena->nextarena =  
  28.                 ao->nextarena;  
  29.         }  
  30.         if (ao->nextarena != NULL) {  
  31.             assert(ao->nextarena->prevarena == ao);  
  32.             ao->nextarena->prevarena =  
  33.                 ao->prevarena;  
  34.         }  
  35.         // 头插到unused_arena_objects链表中  
  36.         ao->nextarena = unused_arena_objects 
  37.         unused_arena_objects = ao  
  38.         // 释放内存  
  39.         _PyObject_Arena.free(_PyObject_Arena.ctx,  
  40.                              (void *)ao->address, ARENA_SIZE);       
  41.         // “arena尚未被分配”的标记  
  42.         ao->address = 0;                      
  43.          --narenas_currently_allocated;  
  44.         return;  
  45.     }  
  46.    //  情况2、所以有pool是full/used状态,释放一个block使得used-empty状态,就此有唯一的empty状态的pool  
  47.    //  需要加入usable_arenas链表中  
  48.     if (nf == 1) {  
  49.         ao->nextarena = usable_arenas 
  50.         ao->prevarena = NULL 
  51.         if (usable_arenas)  
  52.             usable_arenas->prevarena = ao 
  53.         usable_arenas = ao 
  54.         assert(usable_arenas->address != 0);  
  55.         if (nfp2lasta[1] == NULL) {  
  56.             nfp2lasta[1] = ao;  
  57.         }  
  58.         return;  
  59.     }  
  60.      
  61.        
  62.     if (nfp2lasta[nf] == NULL) {  
  63.         nfp2lasta[nf] = ao;  
  64.     
  65.    if (ao == lastnf) {  
  66.         return;  
  67.     } 
  68.     // 情况3、因为usable_arenas维护的是有序表,插入响应的位置  
  69.     if (ao->prevarena != NULL) {  
  70.           
  71.         ao->prevarena->nextarena = ao->nextarena;  
  72.     }  
  73.     else {  
  74.           
  75.         usable_arenas = ao->nextarena;  
  76.     }  
  77.     ao->nextarena->prevarena = ao->prevarena;  
  78.       
  79.     ao->prevarena = lastnf
  80.      ao->nextarena = lastnf->nextarena;  
  81.     if (ao->nextarena != NULL) {  
  82.         ao->nextarena->prevarena = ao 
  83.     }  
  84.     lastnf->nextarena = ao 
  85.       
  86.     assert(ao->nextarena == NULL || nf <= ao->nextarena->nfreepools);  
  87.     assert(ao->prevarena == NULL || nf > ao->prevarena->nfreepools);  
  88.     assert(ao->nextarena == NULL || ao->nextarena->prevarena == ao);  
  89.     assert((usable_arenas == ao && ao->prevarena == NULL)  
  90.            || ao->prevarena->nextarena == ao);  

情况3

Python1、2层内存内存管理汇总

对象特有的分配器(第3层)

对象有列表和元组等多种多样的型,在生成它们的时候要使用各自特有的分配器。见我的其他Python底层数据结构的分析。 

 

来源:马哥Linux运维内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯