zoukankan      html  css  js  c++  java
  • Python 源码学习之内存管理 -- (转)

    Python 的内存管理架构(Objects/obmalloc.c):

        _____   ______   ______       ________
       [ int ] [ dict ] [ list ] ... [ string ]       Python core         |
    +3 | <----- Object-specific memory -----> | <-- Non-object memory --> |
        _______________________________       |                           |
       [   Python's object allocator   ]      |                           |
    +2 | ####### Object memory ####### | <------ Internal buffers ------> |
        ______________________________________________________________    |
       [          Python's raw memory allocator (PyMem_ API)          ]   |
    +1 | <----- Python memory (under PyMem manager's control) ------> |   |
        __________________________________________________________________
       [    Underlying general-purpose allocator (ex: C library malloc)   ]
     0 | <------ Virtual memory allocated for the python process -------> |
    • 0. C语言库函数提供的接口
    • 1. PyMem_*家族,是对 C中的 malloc、realloc和free 简单的封装,提供底层的控制接口。

    • 2. PyObject_* 家族,高级的内存控制接口。

    • 3. 对象类型相关的管理接口

    PyMem_*

    PyMem_家族:低级的内存分配接口(low-level memory allocation interfaces)

    Python 对C中的 malloc、realloc和free 提供了简单的封装:

    C

    Python函数

    Python宏

    malloc

    PyMem_Malloc

    PyMem_MALLOC

    realloc

    PyMem_Realloc

    PyMem_REALLOC

    free

    PyMem_Free

    PyMem_FREE

    为什么要这么多次一举:

    • 不同的C实现对于malloc(0)产生的结果有会所不同,而PyMem_MALLOC(0)会转成malloc(1).

    • 不用的C实现的malloc与free混用会有潜在的问题。python提供封装可以避免这个问题。
      • Python提供了宏和函数,但是宏无法避免这个问题,故编写扩展是应避免使用宏

    源码:

    • Include/pymem.h

     

    #define PyMem_MALLOC(n) ((size_t)(n) > (size_t)PY_SSIZE_T_MAX ? NULL 
                             : malloc((n) ? (n) : 1))
    #define PyMem_REALLOC(p, n) ((size_t)(n) > (size_t)PY_SSIZE_T_MAX  ? NULL 
                               : realloc((p), (n) ? (n) : 1))
    #define PyMem_FREE free
    • Objects/object.c

     

    /* Python's malloc wrappers (see pymem.h) */
    
    void *
    PyMem_Malloc(size_t nbytes)
    {
        return PyMem_MALLOC(nbytes);
    }
    ...

    除了对C的简单封装外,Python还提供了4个宏

    • PyMem_New 和 PyMem_NEW

    • PyMem_Resize和 PyMem_RESIZE

    它们可以感知类型的大小

    #define PyMem_New(type, n) 
      ( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :      
            ( (type *) PyMem_Malloc((n) * sizeof(type)) ) )
    
    #define PyMem_Resize(p, type, n) 
      ( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :        
            (type *) PyMem_Realloc((p), (n) * sizeof(type)) )
    #define PyMem_Del               PyMem_Free
    #define PyMem_DEL               PyMem_FREE

    以下涉及的一些函数仍旧是函数和宏同时存在,下划线后全是大写字符的是宏,后面不再特别说明。

    PyObject_*

    PyObject_*家族,是高级的内存控制接口(high-level object memory interfaces)。

    • 注意

    • 不要和PyMem_*家族混用!!

    • 除非有特殊的内粗管理要求,否则应该坚持使用PyObject_*

    源码

    • Include/objimpl.h

     

    #define PyObject_New(type, typeobj) 
                    ( (type *) _PyObject_New(typeobj) )
    #define PyObject_NewVar(type, typeobj, n) 
                    ( (type *) _PyObject_NewVar((typeobj), (n)) )
    • Objects/object.c

     

    PyObject *
    _PyObject_New(PyTypeObject *tp)
    {
        PyObject *op;
        op = (PyObject *) PyObject_MALLOC(_PyObject_SIZE(tp));
        if (op == NULL)
            return PyErr_NoMemory();
        return PyObject_INIT(op, tp);
    }
    
    PyVarObject *
    _PyObject_NewVar(PyTypeObject *tp, Py_ssize_t nitems)
    {
        PyVarObject *op;
        const size_t size = _PyObject_VAR_SIZE(tp, nitems);
        op = (PyVarObject *) PyObject_MALLOC(size);
        if (op == NULL)
            return (PyVarObject *)PyErr_NoMemory();
        return PyObject_INIT_VAR(op, tp, nitems);
    }

    它们执行两项操作:

    • 分配内存:PyObject_MALLOC

    • 部分初始化对象:PyObject_INITPyObject_INIT_VAR

    初始化没什么好看到,但是这个MALLOC就有点复杂无比了...

    PyObject_{Malloc、Free}

    这个和PyMem_*中的3个可是大不一样了,复杂的厉害!

    void * PyObject_Malloc(size_t nbytes)
    void * PyObject_Realloc(void *p, size_t nbytes)
    void PyObject_Free(void *p)

    Python程序运行时频繁地需要创建和销毁小对象,为了避免大量的malloc和free操作,Python使用了内存池的技术。

    • 一系列的 arena(每个管理256KB) 构成一个内存区域的链表
    • 每个 arena 有很多个 pool(每个4KB) 构成
    • 每次内存的申请释放将在一个 pool 内进行

    单次申请内存块

    当申请大小在 1~256 字节之间的内存时,使用内存池(申请0或257字节以上时,将退而使用我们前面提到的PyMem_Malloc)。

    每次申请时,实际分配的空间将按照某个字节数对齐,下表中为8字节(比如PyObject_Malloc(20)字节将分配24字节)。

      Request in bytes     Size of allocated block      Size class idx
      ----------------------------------------------------------------
             1-8                     8                       0
             9-16                   16                       1
            17-24                   24                       2
            25-32                   32                       3
            33-40                   40                       4
             ...                   ...                     ...
           241-248                 248                      30
           249-256                 256                      31
     
           0, 257 and up: routed to the underlying allocator.

    这些参数由一些宏进行控制:

    #define ALIGNMENT               8               /* must be 2^N */
    /* Return the number of bytes in size class I, as a uint. */
    #define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT)
    #define SMALL_REQUEST_THRESHOLD 256

    pool

    每次申请的内存块都是需要在 pool 中进行分配,一个pool的大小是 4k。由下列宏进行控制:

     

    #define SYSTEM_PAGE_SIZE        (4 * 1024)
    #define POOL_SIZE               SYSTEM_PAGE_SIZE        /* must be 2^N */

    每个pool的头部的定义如下:

    struct pool_header {
        union { block *_padding;
                uint count; } ref;          /* number of allocated blocks    */
        block *freeblock;                   /* pool's free list head         */
        struct pool_header *nextpool;       /* next pool of this size class  */
        struct pool_header *prevpool;       /* previous pool       ""        */
        uint arenaindex;                    /* index into arenas of base adr */
        uint szidx;                         /* block size class index        */
        uint nextoffset;                    /* bytes to virgin block         */
        uint maxnextoffset;                 /* largest valid nextoffset      */
    };

    注意,其中有个成员 szidx,对应前面列表中最后一列的 Size class idx。这也说明一个问题:每个 pool 只能分配固定大小的内存块(比如,只分配16字节的块,或者只分配24字节的块...)。

    要能分配前面列表中各种大小的内存块,必须有多个 pool。同一大小的pool分配完毕,也需要新的pool。多个pool依次构成一个链表

    arena

    多个pool对象使用被称为 arena 的东西进行管理。

    struct arena_object {
        uptr address;
        block* pool_address;
        uint nfreepools;
        uint ntotalpools;
        struct pool_header* freepools;
        struct arena_object* nextarena;
        struct arena_object* prevarena;
    };

    arean控制的内存的大小由下列宏控制:

     

    #define ARENA_SIZE              (256 << 10)     /* 256KB */

    一系列的 arena 构成一个链表。

    引用计数与垃圾收集

    Python中多数对象的生命周期是通过引用计数来控制的,从而实现了内存的动态管理。

    但是引用计数有一个致命的问题:循环引用!

    为了打破循环引用,Python引入了垃圾收集技术。

    这个好复杂啊...

    参考

    文章来源:http://blog.csdn.net/dbzhang800/article/details/6685269

    本文基于Python2.7.5源码中的obmalloc.c模块

    在 Python 的内部系统中,它的内存管理结构是以金子塔结构呈现的.如下图所示:

    QQ20131108-2.png

    • 其中-1和-2这两层是跟操作系统来负责的,这里我们略过不表.
    • 第0层就是我们平常在 C 中使用的 malloc, Python 不会直接使用它,而是会在此基础上做一个内存池.
    • 第1层是 Python 自己在基于 malloc 的基础上构造的一个内存池.
    • 第2和第3层是基于第1层的.每当 Python 内部需要使用内存时,会使用第1层做好的分配器来分配内存. 因此第1层是 Python 内部管理内存的主要地方.

    作用

    在 C 中如果频繁的调用 malloc 与 free 时,是会产生性能问题的.再加上频繁的分配与释放小块的内存会产生内存碎片. Python 在这里主要干的工作有:
    1. 如果请求分配的内存在1~256字节之间就使用自己的内存管理系统,否则直接使用 malloc.
    2. 这里还是会调用 malloc 分配内存,但每次会分配一块大小为256k的大块内存.
    3. 经由内存池登记的内存到最后还是会回收到内存池,并不会调用 C 的 free 释放掉.以便下次使用.

    内存池结构

    temp.png
    如上图所示,整个黑框格子代表内存池(usedpools).每个单元格存放一个双向链表,每个链表的节点是一个大小为4k的内存块.在这个池中,每个单元格负责管理不同的小块内存.为了便于管理,每个单元格管理的内存块总是以8的倍数为单位.以如下代码为例:

    PyObject_Malloc(3)
    

    这里我们需要一块大小为3个字节的内存.它将定位到管理大小为8字节的单元格.然后返回大小8字节的内存.在这里 usedpools 有一个令人蛋疼的 ticky. usedpools 在初始化时用了如下代码:

    #define PTA(x)  ((poolp )((uchar *)&(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)
        ......
    };
    

    如上面所示使用了两个一组的指针来初始化 usedpools, 每次定位到单元格他使用的是 usedpools[idx+idx] 这样来定位的.我也不知道它为什么会使用这么蛋疼且令人费解的设计,连注释都这样写着:

    It's unclear why the usedpools setup is so convoluted.

    分配

    PyObject_Malloc 函数首先会判断进来申请分配的字节数是否在 1<x<256 bytes 这个范围内.然后再从 usedpools 中的管理对应大小的 pool 拿到一块 block, 每个 pool 的大小是4k.每当使用完 pool 中的最后一个 block 时,它会将这个 pool 从 usedpools 剥离出去.
    在调用 malloc 获取内存时,这里做了一层缓存(arenas).每次调用 malloc 会分配一块大小为256k的内存,然后将这块内存分解为一块一块大小为4k的 pool,每当 pool 中 block 用完后,就会重新从 arenas 拿一块 pool 并放入到 usedpools.

    在获取空闲 block 时,这里使用了一个 ticky. pool 中的 freeblock 成员是指向一块空闲的 block. 但这个 freeblock 在空闲时,它里面存了一个地址这个地址指向下一块空闲的 block. 下一块空闲的 block 里同样也存放了一个空闲 block 的地址,以此往下推.直到最后的 block 指向 NULL 为止.

    bp = pool->freeblock;
    if ((pool->freeblock = *(block **)bp) != NULL) {
        UNLOCK();
        return (void *)bp;
    }
    

    如上面所示,拿到一块 block 后,直接获取 freeblock 里面存的地址,并指向它.

    释放

    在释放时会判断将要释放的内存是否属于 usedpools 管理.通常情况下它会直接将这块内存放到的 usedpools 对应 pools 中.如果发现这个 pools 中的 block 全部是 free 状态,它将会返到 arenas .如果 arenas 中的所有的 pool 都为 free 状态的话,则会直接调用 C 中的 free 函数将内存归还与操作系统.否则将这块 arenas 链接到 usable_arenas 正确的位置中.

    文章来源:http://leyafo.logdown.com/posts/159345-python-memory-management

  • 相关阅读:
    Grid如何固定列宽?
    ORACLE 去除重复记录
    Ajax学习之“一头雾水”
    对对碰方块交换及消去效果实现
    存储过程学习(二)
    asp.net 页面重用问题
    一个图表控件
    存储过程学习(一)
    ScriptManager.RegisterClientScriptBlock的疑问
    用indy做发贴机
  • 原文地址:https://www.cnblogs.com/fendou-999/p/3518986.html
Copyright © 2011-2022 走看看