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 的内部系统中,它的内存管理结构是以金子塔结构呈现的.如下图所示:

  • 其中-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 释放掉.以便下次使用.

内存池结构


如上图所示,整个黑框格子代表内存池(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

Python 源码学习之内存管理 -- (转)的更多相关文章

  1. python源码学习(一)——python的总体架构

    python源码学习(一)——python的总体架构 学习环境: 系统:ubuntu 12.04 STLpython版本:2.7既然要学习python的源码,首先我们要在电脑上安装python并且下载 ...

  2. Memcached源码分析之内存管理

    先再说明一下,我本次分析的memcached版本是1.4.20,有些旧的版本关于内存管理的机制和数据结构与1.4.20有一定的差异(本文中会提到). 一)模型分析在开始解剖memcached关于内存管 ...

  3. Python源码学习(一)

    考虑到性能的要求,我在工作中用的最多的是c/c++,然而,工作中又经常会有一些验证性的工作,这些工作对性能的要求并不高,反而对完成的效率要求更高,对于这样的工作,用一种开发效率高的语言是合理的想法,鉴 ...

  4. Python源码学习七 .py文件的解释

    Python源码太复杂了... 今天看了下对.py文件的parse, 云里雾里的 py文件是最简单的, 在python的交互式窗口 import这个模块 a = 10 print(a) 开始分析,堆栈 ...

  5. redis源码解析之内存管理

    zmalloc.h的内容如下: void *zmalloc(size_t size); void *zcalloc(size_t size); void *zrealloc(void *ptr, si ...

  6. Python源码学习Schedule

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  7. 【转】Python源码学习Schedule

    原文:https://www.cnblogs.com/angrycode/p/11433283.html ----------------------------------------------- ...

  8. Mybatis源码学习之事务管理(八)

    简述 在实际开发中,数据库事务的控制是一件非常重要的工作,本文将学习Mybatis对事务的管理机制.在Mybatis中基于接口 Transaction 将事务分为两种,一种是JdbcTransacti ...

  9. Samba 源码解析之内存管理

    由于工作需要想研究下Samba的源码,下载后发现目录结构还是很清晰的.一般大家可能会对source3和source4文件夹比较疑惑.这两个文件夹针对的是Samba主版本号,所以你可以暂时先看一个.这里 ...

随机推荐

  1. djano modles values+ajax实现无页面刷新更新数据

    做项目的过程中想通过不刷新页面的方式来进行页面数据刷新,开始使用http://www.cnblogs.com/ianduin/p/7761400.html方式将查询结果数据进行序列化.发现可以行,但是 ...

  2. jenkins部署springboot多项目

    war包的部署问题不大,这里记录jar包的部署过程: 1:jar包的体积过大问题 pom.xml参考以下配置(依赖包会分离到target/lib/,jar包体积由几十M缩小到几k) <build ...

  3. 【bzoj4721】[Noip2016]蚯蚓 乱搞

    题目描述 本题中,我们将用符号[c]表示对c向下取整,例如:[3.0」= [3.1」=[3.9」=3.蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓.蛐 ...

  4. 算法训练 Bus Tour

    问题描述 想象你是一个在Warsaw的游客,而且预订了一次乘车旅行,去城镇外看一些令人惊异的景点.这辆公共汽车首先围绕城镇行驶一段时间(一段很长的时间,由于Warsaw是一个大城市),把在各自旅馆的人 ...

  5. Android 数据库存储之db4o

    在Android中,使用数据库除了可以使用Android内嵌的SQLite,还可以使用db4odb4o是嵌入式的面向对象的数据库,是基于对象的数据库,操作的数据本身就是对象.特点:对象以其本身的方式来 ...

  6. POJ3581:Sequence——题解

    http://poj.org/problem?id=3581 给一串数,将其分成三个区间并且颠倒这三个区间,使得新数列字典序最小. 参考:http://blog.csdn.net/libin56842 ...

  7. BZOJ1591 & 洛谷2924:[USACO2008 DEC]Largest Fence 最大的围栏——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=1591 https://www.luogu.org/problemnew/show/P2924#sub ...

  8. BZOJ2005:[Noi2010]能量采集——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=2005 Description 栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光 ...

  9. Linux进程间通信简介

    本人仅做简介.转自:http://www.linuxidc.com/Linux/2013-06/85904p2.htm   管道( pipe ):   (Linux进程间通信) 管道是一种半双工的通信 ...

  10. bzoj1706: [Usaco2007 Nov]relays 奶牛接力跑 (Floyd+新姿势)

    题目大意:有t(t<=100)条无向边连接两点,求s到e刚好经过n(n<=10^7)条路径的最小距离. 第一反应分层图,但是一看n就懵逼了,不会写.看了题解之后才知道可以这么玩... 首先 ...