美文网首页
大师兄的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