当前位置: 移动技术网 > IT编程>脚本编程>Python > 从Python的源码浅要剖析Python的内存管理

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

2019年06月14日  | 移动技术网IT编程  | 我要评论

twap.cc,养壁虎,top gear 17季

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)

为什么要这么多次一举:

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网