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


Posted in Python onApril 16, 2015

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

Python 相关文章推荐
用Python登录Gmail并发送Gmail邮件的教程
Apr 17 Python
python保存字符串到文件的方法
Jul 01 Python
Ubuntu下安装PyV8
Mar 13 Python
Python win32com 操作Exce的l简单方法(必看)
May 25 Python
Python实现将SQLite中的数据直接输出为CVS的方法示例
Jul 13 Python
使用python和Django完成博客数据库的迁移方法
Jan 05 Python
Python用 KNN 进行验证码识别的实现方法
Feb 06 Python
基于python3 的百度图片下载器的实现代码
Nov 05 Python
解决Keras 与 Tensorflow 版本之间的兼容性问题
Feb 07 Python
pycharm软件实现设置自动保存操作
Jun 08 Python
python 生成正态分布数据,并绘图和解析
Dec 21 Python
pytorch中F.avg_pool1d()和F.avg_pool2d()的使用操作
May 22 Python
用Python实现换行符转换的脚本的教程
Apr 16 #Python
Python下的subprocess模块的入门指引
Apr 16 #Python
Python下的twisted框架入门指引
Apr 15 #Python
Python代码调试的几种方法总结
Apr 15 #Python
详解Python中with语句的用法
Apr 15 #Python
python获取本机外网ip的方法
Apr 15 #Python
python中常用检测字符串相关函数汇总
Apr 15 #Python
You might like
编写漂亮的代码 - 将后台程序与前端程序分开
2008/04/23 PHP
destoon在360浏览器下出现用户被强行注销的解决方法
2014/06/26 PHP
Web程序工作原理详解
2014/12/25 PHP
windows7下php开发环境搭建图文教程
2015/01/06 PHP
PHP中isset与array_key_exists的区别实例分析
2015/06/02 PHP
PHP获取当前相对于域名目录的方法
2015/06/26 PHP
PHP实现加密文本文件并限制特定页面的存取的效果
2016/10/21 PHP
Yii框架多语言站点配置方法分析【中文/英文切换站点】
2020/04/07 PHP
javascript生成/解析dom的CDATA类型的字段的代码
2007/04/22 Javascript
JS中动态添加事件(绑定事件)的代码
2011/01/09 Javascript
Extjs表单常见验证小结
2014/03/07 Javascript
jQuery中阻止冒泡事件的方法介绍
2014/04/12 Javascript
深入理解JavaScript 函数
2016/06/06 Javascript
正则表达式(语法篇推荐)
2016/06/24 Javascript
JS限制输入框输入的实现代码
2018/07/02 Javascript
Vue2.0中三种常用传值方式(父传子、子传父、非父子组件传值)
2018/08/16 Javascript
微信小程序实现复选框效果
2018/12/28 Javascript
Vue CLI3.0中使用jQuery和Bootstrap的方法
2019/02/28 jQuery
使用Vue开发自己的Chrome扩展程序过程详解
2019/06/21 Javascript
vue3.0中的双向数据绑定方法及优缺点
2019/08/01 Javascript
解决layer.msg 不居中 ifram中的问题
2019/09/05 Javascript
Python学习资料
2007/02/08 Python
如何在Django中添加没有微秒的 DateTimeField 属性详解
2019/01/30 Python
jupyter notebook 使用过程中python莫名崩溃的原因及解决方式
2020/04/10 Python
安装并免费使用Pycharm专业版(学生/教师)
2020/09/24 Python
Python爬虫模拟登陆哔哩哔哩(bilibili)并突破点选验证码功能
2020/12/21 Python
阿根廷票务网站:StubHub阿根廷
2018/04/13 全球购物
EJB的激活机制
2013/10/25 面试题
移动通信行业实习自我鉴定
2013/09/28 职场文书
大学生职业生涯规划书模版
2013/12/30 职场文书
服装电子商务创业计划书
2014/01/30 职场文书
四年级科学教学反思
2014/02/10 职场文书
2015年质检工作总结
2015/05/04 职场文书
2015重阳节敬老活动总结
2015/07/29 职场文书
HTML中table表格拆分合并(colspan、rowspan)
2021/04/07 HTML / CSS
python数据分析之单因素分析线性拟合及地理编码
2022/06/25 Python