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

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

作者: superkmi | 来源:发表于2022-01-13 19:39 被阅读0次

大师兄的Python源码学习笔记(四十九): Python的内存管理机制(四)
[大师兄的Python源码学习笔记(五十一): Python的内存管理机制(六)(https://www.jianshu.com/p/49effa4a619e)]

三、内存池

2. pool的初始化
  • Python启动后,在usedpools这个小块空间内存池中并不存在任何可用的pool
  • 这时Python会采用延迟分配策略,当我们确实开始申请小块内存事,才开始建立这个内存池。
  • 当申请28个字节的内存时,Python实际会申请32字节的内存。
  • Python首先会根据32个字节对应的class size index(3)在usedpools中对应的位置查找。
  • 如果发现在对应的位置后并没有链接任何可用的pool,Python会用useable_arenas链表中的第一个pool
  • 需要特别注意,当前获得的arena中包含的这些pool可能并不属于同一个class size index,因为arenapool不同,没有size class的属性。
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 isn't a pool of the right size class immediately
     * available:  use a free pool.
     */
    if (usable_arenas == NULL) {
        /* No arena has a free pool:  allocate a new arena. */
#ifdef WITH_MEMORY_LIMITS
        if (narenas_currently_allocated >= MAX_ARENAS) {
            goto failed;
        }
#endif
        usable_arenas = new_arena();
        if (usable_arenas == NULL) {
            goto failed;
        }
        usable_arenas->nextarena =
            usable_arenas->prevarena = NULL;
    }
    assert(usable_arenas->address != 0);

    /* Try to get a cached free pool. */
    pool = usable_arenas->freepools;
    if (pool != NULL) {
        /* Unlink from cached pools. */
        usable_arenas->freepools = pool->nextpool;

        /* This arena already had the smallest nfreepools
         * value, so decreasing nfreepools doesn't change
         * that, and we don't need to rearrange the
         * usable_arenas list.  However, if the arena has
         * become wholly allocated, we need to remove its
         * arena_object from usable_arenas.
         */
        --usable_arenas->nfreepools;
        if (usable_arenas->nfreepools == 0) {
            /* Wholly allocated:  remove. */
            assert(usable_arenas->freepools == NULL);
            assert(usable_arenas->nextarena == NULL ||
                   usable_arenas->nextarena->prevarena ==
                   usable_arenas);

            usable_arenas = usable_arenas->nextarena;
            if (usable_arenas != NULL) {
                usable_arenas->prevarena = NULL;
                assert(usable_arenas->address != 0);
            }
        }
        else {
            /* nfreepools > 0:  it must be that freepools
             * isn't NULL, or that we haven't yet carved
             * off all the arena's pools for the first
             * time.
             */
            assert(usable_arenas->freepools != NULL ||
                   usable_arenas->pool_address <=
                   (block*)usable_arenas->address +
                       ARENA_SIZE - POOL_SIZE);
        }

    init_pool:
        ... ...
}

  • 可以看到,如果开始时usable_arenas为空,那么Python会通过new_arena()申请一个arena,开始构建usable_arenas链表。
  • 在这里,一个脱离了unused_arena_objects并转变为usablearena被纳入了usable_arenas的控制。
  • 随后,Python会尝试从usable_arenas链表中的第一个arena锁维护的pool集合中取出一个可用的pool
  • 如果成功取出了这个pool,则进行一些维护信息的更新工作,甚至在当前arena中可用的pool已经用完后,将该arenausable_arenas链表中摘除,当然它还在arena数组中。
  • 如果取出pool失败的话题放在后面。
2.1 初始化一
  • 当我们手中已经有了一块用于32字节内存分配的pool时,为了以后提高内存分配的效率,需要将其放入到usedpools中,这一步叫做init pool:
Objects\obmalloc.c

static int
pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)
{
    ... ...
    init_pool:
        /* Frontlink to used pools. */
        next = usedpools[size + size]; /* == prev */
        pool->nextpool = next;
        pool->prevpool = next;
        next->nextpool = pool;
        next->prevpool = pool;
        pool->ref.count = 1;
        if (pool->szidx == size) {
            /* Luckily, this pool last contained blocks
             * of the same size class, so its header
             * and free list are already initialized.
             */
            bp = pool->freeblock;
            assert(bp != NULL);
            pool->freeblock = *(block **)bp;
            goto success;
        }
        /*
         * Initialize the pool header, set up the free list to
         * contain just the second block, and return the first
         * block.
         */
        pool->szidx = size;
        size = INDEX2SIZE(size);
        bp = (block *)pool + POOL_OVERHEAD;
        pool->nextoffset = POOL_OVERHEAD + (size << 1);
        pool->maxnextoffset = POOL_SIZE - size;
        pool->freeblock = bp + size;
        *(block **)(pool->freeblock) = NULL;
        goto success;
   ... ...
}
  • 在上面代码中,Python将得到的pool放入到usedpools中。
  • 当一个从未被使用的pool被链入usedpools中时,其szidx将被设定为0xFFFF,所以会跳过if (pool->szidx == size)判断。
  • 只有当一个poolempty状态转为used状态之后,由于这时szidx还是其转为empty状态之前的值,所以才会执行if (pool->szidx == size)中的部分。
  • 在以下情况下一个pool会从empty状态转换为used状态:
  • 假设申请的内存size class index为i,且usedpools[i+i]处没有处于used状态的pool,同时在Python维护的全局变量freepools中还有处于emptypool
  • 如果满足上面的条件,那么位于freepools所维护的pool链表头部的pool将被取出来,放入usedpools中,并从其内部分配一块block
  • 同时,这个pool也就从empty状态转换到了used状态。
Objects\obmalloc.c

static int
pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)
{
    ... ...
    /* Try to get a cached free pool. */
    pool = usable_arenas->freepools;
    if (pool != NULL) {
        /* Unlink from cached pools. */
        usable_arenas->freepools = pool->nextpool;

        /* This arena already had the smallest nfreepools
         * value, so decreasing nfreepools doesn't change
         * that, and we don't need to rearrange the
         * usable_arenas list.  However, if the arena has
         * become wholly allocated, we need to remove its
         * arena_object from usable_arenas.
         */
        --usable_arenas->nfreepools;
        if (usable_arenas->nfreepools == 0) {
            /* Wholly allocated:  remove. */
            assert(usable_arenas->freepools == NULL);
            assert(usable_arenas->nextarena == NULL ||
                   usable_arenas->nextarena->prevarena ==
                   usable_arenas);

            usable_arenas = usable_arenas->nextarena;
            if (usable_arenas != NULL) {
                usable_arenas->prevarena = NULL;
                assert(usable_arenas->address != 0);
            }
        }
        else {
            /* nfreepools > 0:  it must be that freepools
             * isn't NULL, or that we haven't yet carved
             * off all the arena's pools for the first
             * time.
             */
            assert(usable_arenas->freepools != NULL ||
                   usable_arenas->pool_address <=
                   (block*)usable_arenas->address +
                       ARENA_SIZE - POOL_SIZE);
        }
    ... ...
    }
2.2 初始化二
  • PyObject_Mallocnew_arena中得到一个新的arena后,将初始化其中的pool集合,并最终分配一个block:
Objects\obmalloc.c

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

... ...

    /* Carve off a new pool. */
    assert(usable_arenas->nfreepools > 0);
    assert(usable_arenas->freepools == NULL);
    pool = (poolp)usable_arenas->pool_address;
    assert((block*)pool <= (block*)usable_arenas->address +
                             ARENA_SIZE - POOL_SIZE);
    pool->arenaindex = (uint)(usable_arenas - arenas);
    assert(&arenas[pool->arenaindex] == usable_arenas);
    pool->szidx = DUMMY_SIZE_IDX;
    usable_arenas->pool_address += POOL_SIZE;
    --usable_arenas->nfreepools;

    if (usable_arenas->nfreepools == 0) {
        assert(usable_arenas->nextarena == NULL ||
               usable_arenas->nextarena->prevarena ==
               usable_arenas);
        /* Unlink the arena:  it is completely allocated. */
        usable_arenas = usable_arenas->nextarena;
        if (usable_arenas != NULL) {
            usable_arenas->prevarena = NULL;
            assert(usable_arenas->address != 0);
        }
    }

    goto init_pool;

... ...
}
  • 这里首先从新申请的arena中取出一个新的pool
  • 然后设置pool中的arenaindex,这个index实际上就是pool所在的arena位于arenas所指的数组中的序号,它可以用来判断一个block是否在某个pool中。
  • 随后,Python将新得到的poolszidx设置为0xffff,用来表示它没管理过block集合。
  • 接着,调整刚获得arena中的pools集合,甚至可能调整usable_arenas
  • 在完成上面内容后,Python将goto init_pool,完成将pool放入usedpools中的任务。
  • PyObject_Malloc的完整代码如下,其中还有很多细节:
Objects\obmalloc.c

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

#ifdef WITH_VALGRIND
    if (UNLIKELY(running_on_valgrind == -1)) {
        running_on_valgrind = RUNNING_ON_VALGRIND;
    }
    if (UNLIKELY(running_on_valgrind)) {
        return 0;
    }
#endif

    if (nbytes == 0) {
        return 0;
    }
    if (nbytes > SMALL_REQUEST_THRESHOLD) {
        return 0;
    }

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

    /* There isn't a pool of the right size class immediately
     * available:  use a free pool.
     */
    if (usable_arenas == NULL) {
        /* No arena has a free pool:  allocate a new arena. */
#ifdef WITH_MEMORY_LIMITS
        if (narenas_currently_allocated >= MAX_ARENAS) {
            goto failed;
        }
#endif
        usable_arenas = new_arena();
        if (usable_arenas == NULL) {
            goto failed;
        }
        usable_arenas->nextarena =
            usable_arenas->prevarena = NULL;
    }
    assert(usable_arenas->address != 0);

    /* Try to get a cached free pool. */
    pool = usable_arenas->freepools;
    if (pool != NULL) {
        /* Unlink from cached pools. */
        usable_arenas->freepools = pool->nextpool;

        /* This arena already had the smallest nfreepools
         * value, so decreasing nfreepools doesn't change
         * that, and we don't need to rearrange the
         * usable_arenas list.  However, if the arena has
         * become wholly allocated, we need to remove its
         * arena_object from usable_arenas.
         */
        --usable_arenas->nfreepools;
        if (usable_arenas->nfreepools == 0) {
            /* Wholly allocated:  remove. */
            assert(usable_arenas->freepools == NULL);
            assert(usable_arenas->nextarena == NULL ||
                   usable_arenas->nextarena->prevarena ==
                   usable_arenas);

            usable_arenas = usable_arenas->nextarena;
            if (usable_arenas != NULL) {
                usable_arenas->prevarena = NULL;
                assert(usable_arenas->address != 0);
            }
        }
        else {
            /* nfreepools > 0:  it must be that freepools
             * isn't NULL, or that we haven't yet carved
             * off all the arena's pools for the first
             * time.
             */
            assert(usable_arenas->freepools != NULL ||
                   usable_arenas->pool_address <=
                   (block*)usable_arenas->address +
                       ARENA_SIZE - POOL_SIZE);
        }

    init_pool:
        /* Frontlink to used pools. */
        next = usedpools[size + size]; /* == prev */
        pool->nextpool = next;
        pool->prevpool = next;
        next->nextpool = pool;
        next->prevpool = pool;
        pool->ref.count = 1;
        if (pool->szidx == size) {
            /* Luckily, this pool last contained blocks
             * of the same size class, so its header
             * and free list are already initialized.
             */
            bp = pool->freeblock;
            assert(bp != NULL);
            pool->freeblock = *(block **)bp;
            goto success;
        }
        /*
         * Initialize the pool header, set up the free list to
         * contain just the second block, and return the first
         * block.
         */
        pool->szidx = size;
        size = INDEX2SIZE(size);
        bp = (block *)pool + POOL_OVERHEAD;
        pool->nextoffset = POOL_OVERHEAD + (size << 1);
        pool->maxnextoffset = POOL_SIZE - size;
        pool->freeblock = bp + size;
        *(block **)(pool->freeblock) = NULL;
        goto success;
    }

    /* Carve off a new pool. */
    assert(usable_arenas->nfreepools > 0);
    assert(usable_arenas->freepools == NULL);
    pool = (poolp)usable_arenas->pool_address;
    assert((block*)pool <= (block*)usable_arenas->address +
                             ARENA_SIZE - POOL_SIZE);
    pool->arenaindex = (uint)(usable_arenas - arenas);
    assert(&arenas[pool->arenaindex] == usable_arenas);
    pool->szidx = DUMMY_SIZE_IDX;
    usable_arenas->pool_address += POOL_SIZE;
    --usable_arenas->nfreepools;

    if (usable_arenas->nfreepools == 0) {
        assert(usable_arenas->nextarena == NULL ||
               usable_arenas->nextarena->prevarena ==
               usable_arenas);
        /* Unlink the arena:  it is completely allocated. */
        usable_arenas = usable_arenas->nextarena;
        if (usable_arenas != NULL) {
            usable_arenas->prevarena = NULL;
            assert(usable_arenas->address != 0);
        }
    }

    goto init_pool;

success:
    UNLOCK();
    assert(bp != NULL);
    *ptr_p = (void *)bp;
    return 1;

failed:
    UNLOCK();
    return 0;
}

相关文章

网友评论

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

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