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

大师兄的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;
    }

   ... ...
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354