美文网首页IT狗工作室Python中文社区
第6篇CPython内存模型架构-Layer 2 - 内存池缓存

第6篇CPython内存模型架构-Layer 2 - 内存池缓存

作者: 铁甲万能狗 | 来源:发表于2020-06-21 19:24 被阅读0次

    在Python3.x中,Python内部默认的小块内存与大块内存的分界点是512字节,我们知道当小于512字节的内存请求,PyObject_Malloc会在内存池中申请内存,当申请的内存大于512字节,PyObject_Malloc的行为会蜕化为malloc的行为。当与小型对象的内存沈秦,Python会使用arenas所维护的内存空间,那么Python内部对于对于arena的个数是否有闲置?换句话说Python对于这个小块空间内存的尺寸是否闲置,这个取决于用户,Python提供一个编译符号,用于控制是否限制这个内存池的尺寸。

    当Python在WITH_MEMORY_LIMITS编译符号打开的背景下进行编译,Python内部的另一个符号会被激活,这个名为SMALL_MEMORY_LIMIT的符号限制了整个内存池的尺寸,同时也就限制了可以创建的arena的个数,在默认情况下,不论是Win32平台,还是unix平台,这个编译符号都没有打开的

    当我们申请一个28字节的内存时,Python内部会在内存池寻找一块能满足需求的pool,并从中取出一个block,而不会去需找arena,这实际上事由pool和arena自身的属性确定的,pool有一个size概念的内存管理抽象体,一个pool中的block总是有确定的类型尺寸.pool_header结构体定义中有一个szidx就是指定了对应的pool分配出去的块的最小的块单位-类型尺寸(size class),然而arena没有size idx的概念,这意味着同一个arena,在某个时刻,其托管的内存池集合可能是size class为32字节的内存池,而另一个时刻该内存池可能会被重新划分,变为64字节的block。

    我们在讨论单个内存池时,有涉及池状态的概念。这里复习一下

    • used:池中至少由一个block已经正在使用,并且至少由一个block还未被使用,这种状态的内存池由CPython的usedpool统一管理
    • full状态:pool中所有block都已正在使用,这种状态的pool在arena托管的池集合内,但不再arena的freepools链表中。
    • empty状态:pool中的所有状态都未被使用,处于这个状态的pool的集合通过其pool_header结构体的nextpool构成一个链表,这个链表的表头就是arena_object结构体的freepools指针。

    解读usedpools数组

    Python内部通过使用usedpools数组,维护者所有处于used状态的pool。当申请内存size class为N时,Python会通过usedpools查找到与N对应的size idx可用的内存池,从中分配一个类型尺寸为N的块,我们看看Objects/obmalloc.c源代码的第1101行到1130行定义,其中的NB_SMALL_SIZE_CLASSES标识当前的CPython实现有多少个size class,对于CPython3.6之前表示有64种size class,CPython3.7之后有32种size class.

    #define SMALL_REQUEST_THRESHOLD 512
    #define NB_SMALL_SIZE_CLASSES   (SMALL_REQUEST_THRESHOLD / ALIGNMENT)
    
    

    参考如下源代码的第1101行到1130行。

    #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_CLASSE
    

    由于任意一个usedpools元素项的表达式为PT(x)等价于PTA(x),PTA(x),那么usedpools的中间形式均等价如下

    对于CPython 3.6之前的版本,按8字节对齐,上面的usedpools数组形式,等价于以下代码

    static poolp usedpools[142] = {
        PTA(0), PTA(0), PTA(1), PTA(1), PTA(2), PTA(2), PTA(3), PTA(3),
        PTA(4), PTA(4), PTA(5), PTA(5), 
        ...PTA(70),PTA(70)
    }
    

    对于CPython3.7之后的版本,按16字节对齐,上面的usedpools数组形式,等价于以下代码

    static poolp usedpools[78] = {
        PTA(0), PTA(0), PTA(1), PTA(1), PTA(2), PTA(2), PTA(3), PTA(3),
        PTA(4), PTA(4), PTA(5), PTA(5), 
        ...PTA(38),PTA(38)
    }
    

    好了,从任意一个PTA(x)的元素项,等价于((poolp )((uint8_t )&(usedpools[2(x)]) - 2*sizeof(block *))),其实整个usedpools数组的核心难点就是该PTA(x)的宏等价表达式。

    我们不妨使用一些演算的例子,例如我们需要申请一个为28字节的内存,我们通过size class和size indx的换算表,构造一个usedpools的内存模型,这样可以透彻理解usedpools数组的内在含义。


    以CPython 3.6之前的为例,由于8字节对齐,那么28字节对应的size calss 是32,对应的szidx是3,对应的换算代码

    uint size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;
    

    那么看一下源代码Objects/obmalloc.c的第1590行-1610行

    static inline void*
    pymalloc_alloc(void *ctx, size_t nbytes)
    {
        ...
        uint size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;
        poolp pool = usedpools[size + size];
        block *bp;
        ...
    

    更新中....

    相关文章

      网友评论

        本文标题:第6篇CPython内存模型架构-Layer 2 - 内存池缓存

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