欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-04 12:05:51
python 的内存管理架构(objects/obmalloc.c): 复制代码 代码如下:     _____  ...

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

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

为什么要这么多次一举:

  •     不同的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);
}

它们执行两项操作:

  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使用了内存池的技术。

  •     一系列的 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引入了垃圾收集技术。