Python内存管理机制

Python内存管理机制-《源码解析》

Python 内存管理分层架构
/* An object allocator for Python.

Here is an introduction to the layers of the Python memory architecture,
showing where the object allocator is actually used (layer +2), It is
called for every object allocation and deallocation (PyObject_New/Del),
unless the object-specific allocators implement a proprietary allocation
scheme (ex.: ints use a simple free list). This is also the place where
the cyclic garbage collector operates selectively on container objects.

1Object-specific allocators 2_____ ______ ______ ________

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

=========================================================================

_______________________________________________________________________

[ OS-specific Virtual Memory Manager (VMM) ]
-1 | <--- Kernel dynamic storage allocation & management (page-based) ---> |

__________________________________   __________________________________

[ ] [ ]
-2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> |

*/
reference:Objects/obmalloc.c

layer 3: Object-specific memory(int/dict/list/string....)

1 python 实现并维护 2 用户对Python对象的直接操作,主要是各类特定对象的缓冲池机制,缓冲池,比如小整数对象池等等

layer 2: Python's object allocator

    实现了创建/销毁python对象的接口(PyObject_New/Del),涉及对象参数/引用计数等

layer 1: Python's raw memory allocator (PyMem_ API)

1 包装了第0层的内存管理接口,提供同一个raw memory管理接口 2 封装的原因:不同操作系统C行为不一致,保证可移植性,相同语义相同行为 3

layer 0: Underlying general-purpose allocator (ex: C library malloc)

    操作系统提供的内存管理接口,由操作系统实现并管理,Python不能干涉这一层的行为,大内存 分配调用malloc函数分配内存

Python 内存分配策略之-block,pool
Python中有分为大内存和小内存,512K为分界线

大内存使用系统malloc进行分配
小内存使用python内存池进行分配

  1. 如果要分配的内存空间大于 SMALL_REQUEST_THRESHOLD bytes(512 bytes), 将直接使用layer 1的内存分配接口进行分配
  2. 否则, 使用不同的block来满足分配需求
    申请一块大小28字节的内存, 实际从内存中划到32字节的一个block (从size class index为3的pool里面划出)

block
内存块block 是python内存的最小单位

  • For small requests we have the following table:
    *

    • 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
      *

    • 0, SMALL_REQUEST_THRESHOLD + 1 and up: routed to the underlying

    • allocator.
      */

pool
pool内存池,管理block, 一个pool管理着一堆固定大小的内存块,在Python中, 一个pool的大小通常为一个系统内存页. 4kB

define SYSTEM_PAGE_SIZE (4 * 1024)

define SYSTEM_PAGE_SIZE_MASK (SYSTEM_PAGE_SIZE - 1)

define POOL_SIZE SYSTEM_PAGE_SIZE / must be 2^N /

define POOL_SIZE_MASK SYSTEM_PAGE_SIZE_MASK

pool的4kB内存 = pool_header + block集合(N多大小一样的block)

typedef uint8_t block;

/ Pool for small blocks. /
struct pool_header {

1union { block *_padding; 2 uint count; } ref; /* number of allocated blocks */ 3block *freeblock; /* pool's free list head */ 4struct pool_header *nextpool; /* next pool of this size class */ 5struct pool_header *prevpool; /* previous pool "" */ 6uint arenaindex; /* index into arenas of base adr */ 7uint szidx; /* block size class index */ 8uint nextoffset; /* bytes to virgin block */ 9uint maxnextoffset; /* largest valid nextoffset */

};
pool_header 作用

与其他pool链接, 组成双向链表

  1. 维护pool中可用的block, 单链表
  2. 保存 szidx , 这个和该pool中block的大小有关系, (block size=8, szidx=0), (block size=16, szidx=1)...用于内存分配时匹配到拥有对应大小block的pool

pool 初始化
void *
PyObject_Malloc(size_t nbytes)
{
...

1 init_pool: 2 // 1. 连接到 used_pools 双向链表, 作为表头 3 // 注意, 这里 usedpools[0] 保存着 block size = 8 的所有used_pools的表头 4 /* Frontlink to used pools. */ 5 next = usedpools[size + size]; /* == prev */ 6 pool->nextpool = next; 7 pool->prevpool = next; 8 next->nextpool = pool; 9 next->prevpool = pool; 10 pool->ref.count = 1; 11 12 // 如果已经初始化过了...这里看初始化, 跳过 13 if (pool->szidx == size) { 14 /* Luckily, this pool last contained blocks 15 * of the same size class, so its header 16 * and free list are already initialized. 17 */ 18 bp = pool->freeblock; 19 pool->freeblock = *(block **)bp; 20 UNLOCK(); 21 return (void *)bp; 22 } 23 24 25 /* 26 * Initialize the pool header, set up the free list to 27 * contain just the second block, and return the first 28 * block. 29 */ 30 // 开始初始化pool_header 31 // 这里 size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT; 其实是Size class idx, 即szidx 32 pool->szidx = size; 33 34 // 计算获得每个block的size 35 size = INDEX2SIZE(size); 36 37 // 注意 #define POOL_OVERHEAD ROUNDUP(sizeof(struct pool_header)) 38 // bp => 初始化为pool + pool_header size, 跳过pool_header的内存 39 bp = (block *)pool + POOL_OVERHEAD; 40 41 // 计算偏移量, 这里的偏移量是绝对值 42 // #define POOL_SIZE SYSTEM_PAGE_SIZE /* must be 2^N */ 43 // POOL_SIZE = 4kb, POOL_OVERHEAD = pool_header size 44 // 下一个偏移位置: pool_header size + 2 * size 45 pool->nextoffset = POOL_OVERHEAD + (size << 1); 46 // 4kb - size 47 pool->maxnextoffset = POOL_SIZE - size; 48 49 // freeblock指向 bp + size = pool_header size + size 50 pool->freeblock = bp + size; 51 52 // 赋值NULL 53 *(block **)(pool->freeblock) = NULL; 54 UNLOCK(); 55 return (void *)bp; 56 }

pool 进行block分配 - 总体代码
if (pool != pool->nextpool) { //

1 /* 2 * There is a used pool for this size class. 3 * Pick up the head block of its free list. 4 */ 5 ++pool->ref.count; 6 bp = pool->freeblock; // 指针指向空闲block起始位置 7 assert(bp != NULL); 8 9 // 代码-1 10 // 调整 pool->freeblock (假设A节点)指向链表下一个, 即bp首字节指向的下一个节点(假设B节点) , 如果此时!= NULL 11 // 表示 A节点可用, 直接返回 12 if ((pool->freeblock = *(block **)bp) != NULL) { 13 UNLOCK(); 14 return (void *)bp; 15 } 16 17 // 代码-2 18 /* 19 * Reached the end of the free list, try to extend it. 20 */ 21 // 有足够的空间, 分配一个, pool->freeblock 指向后移 22 if (pool->nextoffset <= pool->maxnextoffset) { 23 /* There is room for another block. */ 24 // 变更位置信息 25 pool->freeblock = (block*)pool + 26 pool->nextoffset; 27 pool->nextoffset += INDEX2SIZE(size); 28 29 30 *(block **)(pool->freeblock) = NULL; // 注意, 指向NULL 31 UNLOCK(); 32 33 // 返回bp 34 return (void *)bp; 35 } 36 37 // 代码-3 38 /* Pool is full, unlink from used pools. */ // 满了, 需要从下一个pool获取 39 next = pool->nextpool; 40 pool = pool->prevpool; 41 next->prevpool = pool; 42 pool->nextpool = next; 43 UNLOCK(); 44 return (void *)bp; 45 }

pool进行block分配 -1
内存块尚未分配完, 且此时不存在回收的block, 全新进来的时候, 分配第一块block

(pool->freeblock = (block *)bp) == NULL
当进入代码逻辑2时,表示有空闲的block, 代码2的执行流程图如下

pool进行block分配 - 2 回收了某几个block
回收涉及的代码:

void
PyObject_Free(void *p)
{

1poolp pool; 2block *lastfree; 3poolp next, prev; 4uint size; 5 6pool = POOL_ADDR(p); 7if (Py_ADDRESS_IN_RANGE(p, pool)) { 8 /* We allocated this address. */ 9 LOCK(); 10 /* Link p to the start of the pool's freeblock list. Since 11 * the pool had at least the p block outstanding, the pool 12 * wasn't empty (so it's already in a usedpools[] list, or 13 * was full and is in no list -- it's not in the freeblocks 14 * list in any case). 15 */ 16 assert(pool->ref.count > 0); /* else it was empty */ 17 // p被释放, p的第一个字节值被设置为当前freeblock的值 18 *(block **)p = lastfree = pool->freeblock; 19 // freeblock被更新为指向p的首地址 20 pool->freeblock = (block *)p; 21 22 // 相当于往list中头插入了一个节点 23 24 ... 25}

}
每释放一个block,该blcok就会变成pool->freeblock的头结点, 假设已经连续分配了5块, 第1块和第4块被释放,此时的内存图示如下:

此时再一个block分配调用进来, 执行分配, 进入的逻辑是代码-1

bp = pool->freeblock; // 指针指向空闲block起始位置
// 代码-1
// 调整 pool->freeblock (假设A节点)指向链表下一个, 即bp首字节指向的下一个节点(假设B节点) , 如果此时!= NULL
// 表示 A节点可用, 直接返回
if ((pool->freeblock = (block *)bp) != NULL) {

1UNLOCK(); 2return (void *)bp;

}

pool进行block分配 - 3 pool用完了
pool中内存空间都用完了, 进入代码-3

/ Pool is full, unlink from used pools. / // 满了, 需要从下一个pool获取
next = pool->nextpool;
pool = pool->prevpool;
next->prevpool = pool;
pool->nextpool = next;
UNLOCK();
return (void *)bp;
Python 内存分配策略之-arena
arena: 多个pool聚合的结果, 可放置64个pool

define ARENA_SIZE (256 << 10) / 256KB /

arena结构
一个完整的arena = arena_object + pool集合

/ Record keeping for arenas. /
struct arena_object {

1/* The address of the arena, as returned by malloc. Note that 0 2 * will never be returned by a successful malloc, and is used 3 * here to mark an arena_object that doesn't correspond to an 4 * allocated arena. 5 */ 6uintptr_t address; 7 8/* Pool-aligned pointer to the next pool to be carved off. */ 9block* pool_address; 10 11/* The number of available pools in the arena: free pools + never- 12 * allocated pools. 13 */ 14uint nfreepools; 15 16/* The total number of pools in the arena, whether or not available. */ 17uint ntotalpools; 18 19/* Singly-linked list of available pools. */ 20struct pool_header* freepools; 21 22/* Whenever this arena_object is not associated with an allocated 23 * arena, the nextarena member is used to link all unassociated 24 * arena_objects in the singly-linked `unused_arena_objects` list. 25 * The prevarena member is unused in this case. 26 * 27 * When this arena_object is associated with an allocated arena 28 * with at least one available pool, both members are used in the 29 * doubly-linked `usable_arenas` list, which is maintained in 30 * increasing order of `nfreepools` values. 31 * 32 * Else this arena_object is associated with an allocated arena 33 * all of whose pools are in use. `nextarena` and `prevarena` 34 * are both meaningless in this case. 35 */ 36struct arena_object* nextarena; 37struct arena_object* prevarena;

};
arena_object的作用

  1. 与其他arena连接, 组成双向链表
  2. 维护arena中可用的pool, 单链表
    pool_header和管理的blocks内存是一块连续的内存 => pool_header被申请时,其管理的的block集合的内存一并被申请 uint maxnextoffset; / largest valid nextoffset /

arena_object 和其管理的内存是分离的 => arena_object被申请时,其管理的pool集合的内存没有被申请,而是在某一时刻建立关系的

arena的两种状态
/* The head of the singly-linked, NULL-terminated list of available

  • arena_objects.
    */

// 单链表
static struct arena_object* unused_arena_objects = NULL;

/* The head of the doubly-linked, NULL-terminated at each end, list of

  • arena_objects associated with arenas that have pools available.
    */

// 双向链表
static struct arena_object* usable_arenas = NULL;
arena 初始化

  • Allocate a new arena. If we run out of memory, return NULL. Else

    • allocate a new arena, and return the address of an arena_object
    • describing the new arena. It's expected that the caller will set
    • usable_arenas to the return value.
      */

static struct arena_object*
new_arena(void)
{

1struct arena_object* arenaobj; 2uint excess; /* number of bytes above pool alignment */ 3void *address; 4static int debug_stats = -1; 5 6if (debug_stats == -1) { 7 const char *opt = Py_GETENV("PYTHONMALLOCSTATS"); 8 debug_stats = (opt != NULL && *opt != '\0'); 9} 10if (debug_stats) 11 _PyObject_DebugMallocStats(stderr); 12 13// 判断是否需要扩充"未使用"的arena_object列表 14if (unused_arena_objects == NULL) { 15 uint i; 16 uint numarenas; 17 size_t nbytes; 18 19 /* Double the number of arena objects on each allocation. 20 * Note that it's possible for `numarenas` to overflow. 21 */ 22 // 确定需要申请的个数, 首次初始化, 16, 之后每次翻倍 23 numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS; 24 if (numarenas <= maxarenas) 25 return NULL; /* overflow */

if SIZEOF_SIZE_T <= SIZEOF_INT

1 if (numarenas > SIZE_MAX / sizeof(*arenas)) 2 return NULL; /* overflow */

endif

1 nbytes = numarenas * sizeof(*arenas); 2 // 申请内存 3 arenaobj = (struct arena_object *)PyMem_RawRealloc(arenas, nbytes); 4 if (arenaobj == NULL) 5 return NULL; 6 arenas = arenaobj; 7 8 /* We might need to fix pointers that were copied. However, 9 * new_arena only gets called when all the pages in the 10 * previous arenas are full. Thus, there are *no* pointers 11 * into the old array. Thus, we don't have to worry about 12 * invalid pointers. Just to be sure, some asserts: 13 */ 14 assert(usable_arenas == NULL); 15 assert(unused_arena_objects == NULL); 16 17 /* Put the new arenas on the unused_arena_objects list. */ 18 for (i = maxarenas; i < numarenas; ++i) { 19 arenas[i].address = 0; /* mark as unassociated */ 20 // 新申请的一律为0, 标识着这个arena处于"未使用" 21 arenas[i].nextarena = i < numarenas - 1 ? 22 &arenas[i+1] : NULL; 23 } 24 25 // 将其放入unused_arena_objects链表中 26 // unused_arena_objects 为新分配内存空间的开头 27 /* Update globals. */ 28 unused_arena_objects = &arenas[maxarenas]; 29 maxarenas = numarenas; 30} 31 32/* Take the next available arena object off the head of the list. */ 33assert(unused_arena_objects != NULL); 34// 从unused_arena_objects中, 获取一个未使用的object 35arenaobj = unused_arena_objects; 36unused_arena_objects = arenaobj->nextarena; // 更新链表 37assert(arenaobj->address == 0); 38// 申请内存, 256KB, 内存地址赋值给arena的address. 这块内存可用 39address = _PyObject_Arena.alloc(_PyObject_Arena.ctx, ARENA_SIZE); 40if (address == NULL) { 41 /* The allocation failed: return NULL after putting the 42 * arenaobj back. 43 */ 44 arenaobj->nextarena = unused_arena_objects; 45 unused_arena_objects = arenaobj; 46 return NULL; 47} 48arenaobj->address = (uintptr_t)address; 49 50++narenas_currently_allocated; 51++ntimes_arena_allocated; 52if (narenas_currently_allocated > narenas_highwater) 53 narenas_highwater = narenas_currently_allocated; 54arenaobj->freepools = NULL; 55/* pool_address <- first pool-aligned address in the arena 56 nfreepools <- number of whole pools that fit after alignment */ 57arenaobj->pool_address = (block*)arenaobj->address; 58arenaobj->nfreepools = MAX_POOLS_IN_ARENA; 59// 将pool的起始地址调整为系统页的边界 60// 申请到 256KB, 放弃了一些内存, 而将可使用的内存边界pool_address调整到了与系统页对齐 61excess = (uint)(arenaobj->address & POOL_SIZE_MASK); 62if (excess != 0) { 63 --arenaobj->nfreepools; 64 arenaobj->pool_address += POOL_SIZE - excess; 65} 66arenaobj->ntotalpools = arenaobj->nfreepools; 67 68return arenaobj;

}

从arenas取一个arena进行初始化

arena分配
new一个全新的arena

static void*
pymalloc_alloc(void *ctx, size_t nbytes)
{

1 // 刚开始没有可用的arena 2 if (usable_arenas == NULL) { 3 // new一个, 作为双向链表的表头 4 usable_arenas = new_arena(); 5 if (usable_arenas == NULL) { 6 UNLOCK(); 7 goto redirect; 8 } 9 10 usable_arenas->nextarena = 11 usable_arenas->prevarena = NULL; 12 13 } 14 15 ....... 16 17 // 从arena中获取一个pool 18 pool = (poolp)usable_arenas->pool_address; 19 assert((block*)pool <= (block*)usable_arenas->address + 20 ARENA_SIZE - POOL_SIZE); 21 pool->arenaindex = usable_arenas - arenas; 22 assert(&arenas[pool->arenaindex] == usable_arenas); 23 pool->szidx = DUMMY_SIZE_IDX; 24 25 // 更新 pool_address 向下一个节点 26 usable_arenas->pool_address += POOL_SIZE; 27 // 可用节点数量-1 28 --usable_arenas->nfreepools;

}
从全新的arena中获取一个pool

假设arena是旧的, 怎么分配的pool, 跟pool分配block原理一样,使用单链表记录freepools

pool = usable_arenas->freepools;
if (pool != NULL) {
当arena中一整块pool被释放的时候

/* Free a memory block allocated by pymalloc_alloc().
Return 1 if it was freed.
Return 0 if the block was not allocated by pymalloc_alloc(). */
static int
pymalloc_free(void ctx, void p) {

1struct arena_object* ao; 2uint nf; /* ao->nfreepools */ 3 4/* Link the pool to freepools. This is a singly-linked 5 * list, and pool->prevpool isn't used there. 6 */ 7ao = &arenas[pool->arenaindex]; 8pool->nextpool = ao->freepools; 9ao->freepools = pool; 10nf = ++ao->nfreepools;

}
在pool整块被释放的时候, 会将pool加入到arena->freepools作为单链表的表头, 然后, 在从非全新arena中分配pool时, 优先从arena->freepools里面取, 如果取不到, 再从arena内存块里面获取

注: 上图中nfreepools = n - 2

当arena1用完了,获取arena1指向的下一个节点arena2

static void*
pymalloc_alloc(void *ctx, size_t nbytes)
{

1 // 当发现用完了最后一个pool!!!!!!!!!!! 2 // nfreepools = 0 3 if (usable_arenas->nfreepools == 0) { 4 assert(usable_arenas->nextarena == NULL || 5 usable_arenas->nextarena->prevarena == 6 usable_arenas); 7 /* Unlink the arena: it is completely allocated. */ 8 9 // 找到下一个节点! 10 usable_arenas = usable_arenas->nextarena; 11 // 右下一个 12 if (usable_arenas != NULL) { 13 usable_arenas->prevarena = NULL; // 更新下一个节点的prevarens 14 assert(usable_arenas->address != 0); 15 } 16 // 没有下一个, 此时 usable_arenas = NULL, 下次进行内存分配的时候, 就会从arenas数组中取一个 17 18 }

}
注意: 这里有个逻辑, 就是每分配一个pool, 就检查是不是用到了最后一个, 如果是, 需要变更usable_arenas到下一个可用的节点, 如果没有可用的, 那么下次进行内存分配的时候, 会判定从arenas数组中取一个

arena回收
内存分配和回收最小单位是block, 当一个block被回收的时候, 可能触发pool被回收, pool被回收, 将会触发arena的回收机制

arena中所有pool都是闲置的(empty), 将arena内存释放, 返回给操作系统
如果arena中之前所有的pool都是占用的(used), 现在释放了一个pool(empty), 需要将 arena加入到usable_arenas, 会加入链表表头
如果arena中empty的pool个数n, 则从useable_arenas开始寻找可以插入的位置. 将arena插入. (useable_arenas是一个有序链表, 按empty pool的个数, 保证empty pool数量越多, 被使用的几率越小, 最终被整体释放的机会越大)
内存分配的步骤
关注点:如何寻找到一块可用的nbytes的blcok内存?

pool = usedpools[size + size]

if pool:

​ pool 没满,取一个blcok返回

​ pool 满了,从下一个pool取一个blcok返回

else:

​ 获取arena, 从里面初始化一个pool, 拿到第一个blcok返回

进行内存分配和销毁, 所有操作都是在pool上进行的

问题: pool中所有block的size一样, 但是在arena中, 每个pool的size都可能不一样, 那么最终这些pool是怎么维护的? 怎么根据大小找到需要的block所在的pool? => usedpools

pool在内存池中的三种状态
used状态:pool中至少有一个block已经被使用,并且至少有一个block未被使用,这种状态的pool受控于Python内部维护的usedpool数组
full状态:pool中所有的block都已经被使用,这种状态的pool在arena中, 但不在arena的freepools链表中,处于full的pool各自独立, 不会被链表维护起来
empty状态:pool中所有的blcok都未被使用,处于这个状态的pool的集合通过其pool_header中的nextpool构成一个链表,链表的表头示arena_object中的freepools

Python内部维护的usedpools数组是一个非常巧妙的实现,维护着所有的处于used状态的pool,当申请内存时,python就会通过usedpools寻找到一个可用的pool(处于used状态),从中分配一个block。因此我们想,一定有一个usedpools相关联的机制,完成从申请的内存的大小到size class index之间的转换,否则python就无法找到最合适的pool了。这种机制和usedpools的结构有着密切的关系,我们看一下它的结构

usedpools
usedpools数组: 维护着所有处于used状态的pool, 当申请内存的时候, 会通过usedpools寻找到一块可用的(处于used状态的)pool, 从中分配一个block。

//obmalloc.c
typedef uint8_t block;

define PTA(x) ((poolp )((uint8_t )&(usedpools[2(x)]) - 2_sizeof(block_ )))

define PT(x) PTA(x), PTA(x)

//在我当前的机器就是512/8=64个,对应的size class index就是从0到63

define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT)

static poolp usedpools[2 ((NB_SMALL_SIZE_CLASSES + 7) / 8) 8] = {

PT(0), PT(1), PT(2), PT(3), PT(4), PT(5), PT(6), PT(7)

if NB_SMALL_SIZE_CLASSES > 8

, PT(8), PT(9), PT(10), PT(11), PT(12), PT(13), PT(14), PT(15)

if NB_SMALL_SIZE_CLASSES > 16

, PT(16), PT(17), PT(18), PT(19), PT(20), PT(21), PT(22), PT(23)

if NB_SMALL_SIZE_CLASSES > 24

, PT(24), PT(25), PT(26), PT(27), PT(28), PT(29), PT(30), PT(31)

if NB_SMALL_SIZE_CLASSES > 32

, PT(32), PT(33), PT(34), PT(35), PT(36), PT(37), PT(38), PT(39)

if NB_SMALL_SIZE_CLASSES > 40

, PT(40), PT(41), PT(42), PT(43), PT(44), PT(45), PT(46), PT(47)

if NB_SMALL_SIZE_CLASSES > 48

, PT(48), PT(49), PT(50), PT(51), PT(52), PT(53), PT(54), PT(55)

if NB_SMALL_SIZE_CLASSES > 56

, PT(56), PT(57), PT(58), PT(59), PT(60), PT(61), PT(62), PT(63)

if NB_SMALL_SIZE_CLASSES > 64

error "NB_SMALL_SIZE_CLASSES should be less than 64"

endif / NB_SMALL_SIZE_CLASSES > 64 /

endif / NB_SMALL_SIZE_CLASSES > 56 /

endif / NB_SMALL_SIZE_CLASSES > 48 /

endif / NB_SMALL_SIZE_CLASSES > 40 /

endif / NB_SMALL_SIZE_CLASSES > 32 /

endif / NB_SMALL_SIZE_CLASSES > 24 /

endif / NB_SMALL_SIZE_CLASSES > 16 /

endif / NB_SMALL_SIZE_CLASSES > 8 /

};

如果正在申请28字节, python首先会获取(size class index) size = (uint )(nbytes - 1) >> ALIGNMENT_SHIFT 显然这里size=3, 那么在usedpools中,寻找第3+3=6个元素,发现usedpools[6]的值是指向usedpools[4]的地址

//obmalloc.c
/ Pool for small blocks. /
struct pool_header {

1union { block *_padding; 2 uint count; } ref; /* 当然pool里面的block数量 */ 3block *freeblock; /* 一个链表,指向下一个可用的block */ 4struct pool_header *nextpool; /* 指向下一个pool */ 5struct pool_header *prevpool; /* 指向上一个pool "" */ 6uint arenaindex; /* 在area里面的索引 */ 7uint szidx; /* block的大小(固定值?后面说) */ 8uint nextoffset; /* 下一个可用block的内存偏移量 */ 9uint maxnextoffset; /* 最后一个block距离开始位置的距离 */

};

显然是从usedpools6开始向后偏移8个字节(一个ref的大小加上一个freeblock的大小)后的内存,正好是usedpools[6]的地址(即usedpools+6),这是python内部的trick

当我们要申请一个size class为32字节的pool,想要将其放入这个usedpools中时,要怎么做呢?从上面的描述我们知道,只需要进行usedpools[i+i] -> nextpool = pool即可,其中i为size class index,对应于32字节,这个i为3.当下次需要访问size class 为32字节(size class index为3)的pool时,只需要简单地访问usedpools[3+3]就可以得到了。python正是使用这个usedpools快速地从众多的pool中快速地寻找到一个最适合当前内存需求的pool,从中分配一块block。

//obmalloc.c
static int
pymalloc_alloc(void ctx, void *ptr_p, size_t nbytes)
{

1block *bp; 2poolp pool; 3poolp next; 4uint size; 5... 6LOCK(); 7//获得size class index 8size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT; 9//直接通过usedpools[size+size],这里的size不就是我们上面说的i吗? 10pool = usedpools[size + size]; 11//如果usedpools中有可用的pool 12if (pool != pool->nextpool) { 13 ... //有可用pool 14} 15... //无可用pool,尝试获取empty状态的pool

}
内存池全局结构

参考:

pyhton源码阅读-内存管理机制

python源码解析第17章-python内存管理与垃圾回收

原文地址https://www.cnblogs.com/panlq/p/13056907.html

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )