美文网首页
大师兄的Python源码学习笔记(四十九): Python的内存

大师兄的Python源码学习笔记(四十九): Python的内存

作者: superkmi | 来源:发表于2022-01-07 14:24 被阅读0次

大师兄的Python源码学习笔记(四十八): Python的内存管理机制(三)
大师兄的Python源码学习笔记(五十): Python的内存管理机制(五)

三、内存池

1. 可用pool缓冲池:usedpools
  • 前面我们提到过,Python中的大块内存和小块内存分界点定在512个字节,这个分界点是由SMALL_REQUEST_THRESHOLD控制。
Objects/obmalloc.c

#define SMALL_REQUEST_THRESHOLD 512
  • 当申请内存小于512个字节时,PyObject_Malloc会在内存池中申请内存。
  • 当申请内存大于512个字节时,PyObject_Malloc的行为讲蜕化为malloc的行为。
  • 当申请小于512字节的内存时,会使用arena所维护的内存空间,而arena的个数是否有限制,取决于用户。
  • 当在WITH_MEMORY_LIMITS编译符号打开的背景下进行编译时,Python内部的另一个符号SMALL_MEMORY_LIMITS将被激活,它限制了可以创建的arena个数
/*
* Maximum amount of memory managed by the allocator for small requests.
*/
#ifdef WITH_MEMORY_LIMITS
#ifndef SMALL_MEMORY_LIMIT
#define SMALL_MEMORY_LIMIT      (64 * 1024 * 1024)      /* 64 MB -- more? */
#endif
#endif

/*
* The allocator sub-allocates <Big> blocks of memory (called arenas) aligned
* on a page boundary. This is a reserved virtual address space for the
* current process (obtained through a malloc()/mmap() call). In no way this
* means that the memory arenas will be used entirely. A malloc(<Big>) is
* usually an address range reservation for <Big> bytes, unless all pages within
* this space are referenced subsequently. So malloc'ing big blocks and not
* using them does not mean "wasting memory". It's an addressable range
* wastage...
*
* Arenas are allocated with mmap() on systems supporting anonymous memory
* mappings to reduce heap fragmentation.
*/
#define ARENA_SIZE              (256 << 10)     /* 256KB */

#ifdef WITH_MEMORY_LIMITS
#define MAX_ARENAS              (SMALL_MEMORY_LIMIT / ARENA_SIZE)
#endif
  • 在默认情况下,Win32平台和unix平台都会关闭WITH_MEMORY_LIMITS,所以通常Python没有对小块内存的内存池做任何限制。
  • 尽管arena是小块内存池的最上层结构,但在实际使用中,Python的基本操作单元是pool
  • 这是因为pool是一个有size概念的内存管理抽象体,pool中的block总有确定的大小,并和size class index对应。
  • arena是没有size概念的内存管理抽象体,同一个arena在某个时刻,其内的pool集合管理的block的大小可能根据系统需要而不同。
  • 此外,内存池中的pool还是一个有状态的内存管理抽象体,一个pool在任何时刻,总是处于以下三种状态中的一种:
  • used状态pool中至少有一个block已经被使用,并且至少有一个block还未被使用,这种状态的pool受控于Python内部维护的usedpools数组。
  • full状态pool中所有的block都已经被使用,这种状态的poolarena中,但不在arenafreepools链表中。
  • empty状态pool中所有的block都未被使用,处于这个状态的pool的集合通过其pool_header中的nextpool构成一个链表,这个链表头就是arena_object中的freepools
  • Python内部维护的usedpools数组巧妙的维护这所有处于used状态的pool,上图中所有处于used状态的pool都被置于usedpools的控制之下。
  • 当申请内存时,Python会通过usedpools寻找一块处于used状态的pool,从中分配一个block
  • 这意味着一定有一个与usedpools相关联的机制,完成从申请的内存的大小到size class index之间的转换,观察usedpools的结构:
Objects/obmalloc.c

#define PTA(x)  ((poolp )((uint8_t *)&(usedpools[2*(x)]) - 2*sizeof(block *)))
#define PT(x)   PTA(x), PTA(x)

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 */
};
  • 其中的NB_SMALL_SIZE_CLASSES指明了在当前的配置下,一共有多少个size class
  • 用一幅图解释usedpools结构:
  • 假设申请28个字节时:
  • Python会首先获得size class index,并通过size = (uint )(nbytes - 1) >> ALIGNMENT_SHIFT得到size class index为3。
  • usedpools中,寻找第3+3=6个元素,发现usedpools[6]的值指向usedpools[4]的地址。
  • 因为usedpools[6]->nextpool指向的是usedpools[4](即usedpool+4)开始向后偏移8个字节(一个ref的大小加上一个freeblock的大小)的内存,也就是usedpools[4](即usedpool+4)的地址。
  • 假设我们手中有一个size class为32字节的pool,想要放入到这个usedpools中时:
  • 只需要进行usedpools[i+i]->nextpool = pool即可。
  • 其中i为size class index,对应32字节,所以i为3。
  • 当下次需要访问size class为32字节的pool时,只需要简单地访问usedpool[3+3]就可以得到了。
  • Python使用usedpools快速地从众多的pools迅速地寻找到一个最适合当前内存需求的pool,从中分配一块block
  • pymalloc_alloc中,Python利用了usedpools的巧妙结构,通过简单的判断来发现与某个class size index对应的pool是否在usedpools中存在:
Objects/obmalloc.c

static int
pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)
{
    block *bp;
    poolp pool;
    poolp next;
    uint size;

... 

    LOCK();
    /*
     * Most frequent paths first
     */
    size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;
    pool = usedpools[size + size];
    if (pool != pool->nextpool) {
        /*
         * There is a used pool for this size class.
         * Pick up the head block of its free list.
         */
        ++pool->ref.count;
        bp = pool->freeblock;
        assert(bp != NULL);
        if ((pool->freeblock = *(block **)bp) != NULL) {
            goto success;
        }

        /*
         * Reached the end of the free list, try to extend it.
         */
        if (pool->nextoffset <= pool->maxnextoffset) {
            /* There is room for another block. */
            pool->freeblock = (block*)pool +
                              pool->nextoffset;
            pool->nextoffset += INDEX2SIZE(size);
            *(block **)(pool->freeblock) = NULL;
            goto success;
        }

        /* Pool is full, unlink from used pools. */
        next = pool->nextpool;
        pool = pool->prevpool;
        next->prevpool = pool;
        pool->nextpool = next;
        goto success;
    }

   ... ...
}

相关文章

网友评论

      本文标题:大师兄的Python源码学习笔记(四十九): Python的内存

      本文链接:https://www.haomeiwen.com/subject/hfmyqrtx.html