从Python的源码浅要剖析Python的内存管理

1174次阅读  |  发布于5年以前

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 提供了简单的封装:

201541692301579.jpg \(301×158\)

为什么要这么多次一举:

源码:


      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)。

注意

源码


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

它们执行两项操作:

  1. 分配内存:PyObject_MALLOC
  2. 部分初始化对象:PyObject_INIT和PyObject_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使用了内存池的技术。

单次申请内存块

当申请大小在 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引入了垃圾收集技术。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8