《python源码剖析》笔记 pythonm内存管理机制

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie

1.内存管理架构

Python的内存管理机制都有两套实现:debug模式和release模式

Python内存管理机制的层次结构:

图16-1

第0层是操作系统提供的内存管理接口,如malloc、free

第1层是Python基于第0层操作系统的内存管理接口包装而成的,主要是为了处理与平台相关的内存分配行为。

实现是一组以PyMem_为前缀的函数族

两套接口:函数和宏。

宏,可以避免函数调用的开销,提高效率,但可能与新版本的python产生二进制不兼容,如果用C来编写Python的

扩展模块,使用函数接口是一个良好的编程习惯

第2层 以PyObje_为前缀的函数族,主要提供创建Python对象的接口。包括了gc内存管理机制

第3层 对象缓冲池机制

2.小块空间的内存池

Pymalloc机制:内存池机制,管理对小块内存的申请和释放,通过PyObject_Malloc、PyObject_Realloc、PyObject_Free

三个接口显示给Python

整个小块内存的内存池可以视为一个层次结构,分为4层,从下至上分别是:block,pool,arena和内存池

Block

所有block的长度都是8字节对齐的(ALIGNMENT)

SMALL_REQUEST_THRESHOLD:当申请的内存小于这个值时,Python可以使用不同种类的block来满足对内存

的需求;当申请的内存大小超过这个上限,转交请求给第一层内存管理机制。

根据 SMALL_REQUEST_THRESHOLD和 ALIGNMENT的限定,可以得到以下结论:

//从size class index转换到size class
#define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT)
//从size class到size class index
size = (uint )(nbytes - 1) >> ALIGNMENT_SHIFT; 

Pool

一个pool管理着一堆有固定大小的内存块(block),这些block的大小都是一样的。

一个pool的大小通常是一个系统内存页,由于当前大多数Python支持的系统的内存页都是4KB,

所以Python内部也将一个pool的大小定义为4KB,这个大小包括pool_header和pool管理的blocks

typedef uchar block;

struct pool_header{
	union{ block *_padding;
		uint count;} ref; //count表示已经分配的block数目
	block *freeblock; //指向下一个可用 block
	struct pool_header *nextpool; //链接下一个pool
	struct pool_header *prevpool; //链接上一个pool
	uint arenaindex;
	uint szidx; //block 大小的index
	uint nextoffset; //指向 freeblock之后的下一个可用的block
	uint maxnextoffset; //指向了pool中最后一个可用的block距pool开始位置的偏移
};

将4KB的内存发行为一个管理32字节block的pool,并从中取出第一块block

#define ROUNDUP(x) (((x) + ALIGNMENT_MASK) & ~ALIGNMENT_MASK)
#define POOL_OVERHEAD ROUNDUP(sizeof(struct pool_header))
#define struct pool_header *poolp
#define uchar block
poolp pool;
block *bp;
//pool指向了一块4KB的内存
pool->ref.count = 1;
//设置pool的size class index
pool->szidx = size;
//将size class index转换为size,比如3转换为32字节
size = INDEX2SIZE(size);
//跳过用于pool_header的内存,并进行对齐
bp = (block *)pool + POOL_OVERHEAD;
//实际就是pool->nextoffset = POOL_OVERHEAD+size + size
pool->nextoffset = POOL_OVERHEAD + (size << 1);
pool->maxnextoffset = POOL_size - size;
pool->freeblock = bp + size;

图16-2

申请内存时,pool_header中各个域的变化

if(pool != pool->nextpool){    ++pool->ref.count;
    bp = pool->freeblock;
    //...
    if(pool->nextoffset <= pool->maxnextoffset){
        //有足够的空间
        pool->freeblock = (block *) pool + pool->nextoffset;
	pool->nextoffset += INDEX2SIZE(size);
	*(block **)(pool->freeblock) = NULL; //建立离散自由block链表的关键所在
	return (void *)bp;
    }
}

自由block链表

当一个block被释放的时候

#define POOL_ADDR(P) ((poolp)((uptr)(p) & ~(uptr)POOL_SIZE_MASK))

void PyObject_Free(void *p){
    poolp pool;
	block *lastfree;
	poolp next, prev;
	uint size;

	pool = POOL_ADDR(p);
	//判断p指向的block是否属于pool
	if(Py_ADDRESS_IN_RANGE(p, pool)){
		*(block **)p = lastfree = pool->freeblock;//[1]
		pool->freeblock = (block *)p;
		//...
	}
}

图16-3

Arena

一个Arena就是多个pool的集合,一个Arena的大小默认是256KB

typedef uchar block;

struct arena_object{
	uptr address;
	block *pool_address;
	uint nfreepools;
	uint ntotalpools;
	struct pool_header *freepools;
	struct arena_object *nextarena;
	struct arena_object *prevarena;
}

pool_header管理的内存和pool_header自身是一块连续的内存,而arena_object与其管理的内存则是分离的。

图16-4

当pool_header被申请时,它所管理的block集合的内存一定也被申请了;但是当 arena_object被申请时,

它所管理的pool集合的内存则没有被申请,需要在某一时刻建立联系。

当一个arena的arena_object没有与Pool集合建立联系时,这里的arena处于”未使用“状态,一旦建立了联系,

这时的arena就转换到了”可用“状态。

”未使用“的arena链表表头是unused_arena_objects,是单向链表

”可用“的arena的链表表头是usable_arenas,是双向链表

图16-5

申请arena

//arenas管理着arena_object的集合
static struct arena_object *arenas = NULL;
//当前arenas中管理的 arena_object的个数
static uint maxarenas = 0;
//"未使用"的 arena_object链表
static struct arena_object *unused_arena_objects = NULL;
//”可用“ 的 arena_object链表
static struct arena_object *usable_arenas = NULL;
//初始化时需主持 arena_object的个数
#define INITIAL_ARENA_OBJECTS 16

static struct arena_object *new_arena(void){
	struct arena_object *arenaobj;
	uint excess;

	//[1]:判断是否需要扩充”未使用“的 arena_object列表
	if(unused_arena_objects == NULL){
		uint i;
		uint numarenas;
		size_t nbytes;

		//[2]:确定本次需要申请的 arena_object的个数,并申请内存
		numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS;
		if(numarenas <= maxarenas)
			return NULL; //溢出
		nbytes = numarenas * sizeof(*arenas);
		if(nbytes / sizeof(*arenas) != numarenas)
			return NULL; //溢出
		arenaobj = (struct arena_object *)realloc(arenas, nbytes);
		if(arenaobj == NULL)
			return NULL;
		arenas = arenaobj;

		//[3]:初始化新申请的 arena_object,并将其放入 unused_arena_objects链表中
		for(i = maxarrenas; i < numarenas; ++i){
			arenas[i].address = 0; //mark as unassociated
			arenas[i].nextarena = i < numarenas - 1 ? &arenas[i + 1] : NULL;
		}
		//update globals
		unused_arena_objects = &arenas[maxarenas];
		maxarenas = numarenas;
	}

	//[4]:从 unused_arena_objects 链表中取出一个“未使用”的arena_object
	arenaobj = unused_arena_objects;
	unused_arena_objects = arenaobj->nextarena;
	assert(arenaobj->address == 0);

	//[5]:申请arena_object管理的内存
	arenaobj->address = (uptr)malloc(ARENA_SIZE);
	++narenas_currently_allocated;

	//[6]:设置pool集合的相关信息
	arenaobj->freepools = NULL;
	arenaobj->pool_address = (block *)arenaobj->address;
	arenaobj->nfreepools = ARENA_SIZE / POOL_SIZE;
	//将pool的起始地址调整为系统页的边界
	excess = (uint)(arenaobj->address & POOL_SIZE_MASK);
	if(excess != 0){
		--arenaobj->nfreepools;
		arenaobj->pool_address += POOL_SIZE - excess;
	}
	arenaobj->ntotalpools = arenaobj->nfreepools;
	return arenaobj;

}

内存池

Python内部默认的小块内存与大块内存的分界点控制在256个字节(SMALL_REQUEST_THREADHOLD)。

当申请的内存小于256字节时,PyObject_Malloc会在内存池中申请内存;当申请的内存大于256字节时,

PyObject_Malloc的行为将蜕化为malloc的行为。

当Python申请内存时,最基本的操作单元是pool,因为pool的pool_head里有个szidx,表示block的大小。

一个pool在python运行的任何一个时刻,总处于以下三种状态的一种:

used状态、full状态、empty状态

处于used状态的pool被置于usedpools的控制之下。

图16-6

//todo

3.循环引用的垃圾收集

Python 使用引用计数进行垃圾回收

优点:实时性,任何内存,一旦没有指向它的引用,就会立即被回收。为了与引用计数机制搭配,在内存的分配与释放上获得最高的效率,

Python设计了大量内存池机制。

缺点:循环引用,使得一组对象的引用计数都不为0。-->解决方法:标记-清除,分代收集

图16-10

三色标记模型

1.寻找root object(全局引用或函数栈中的引用)的集合

2.垃圾检测:从root object 集合出发,沿着root object 集合中的每一个引用,如果能到达某个对象A,则A称为可达的

3.垃圾回收:保留可达对象,删除不可达对象

图16-11

4.Python中的垃圾收集

Python的主要内存管理手段是引用计数机制,为打破循环引用引入了“标记-清除”和“分代收集”

只有container才可能产生循环引用,将创建的container记录在双向链表中

普通Python对象:PyObject_HEAD + 自身数据

container对象:PyGc_Head + PyObject_HEAD + 自身数据

一个container对象想要成为一个可心仪的对象,必须加入PyGc_Head信息

typedef union _gc_head{
	struct{
		union _gc_head *gc_next;
		union _gc_head *gc_prev;
		int gc_refs;
	} gc;
	long double dummy;
} PyGc_Head;

在创建某个container的最后一步,将这个对象链接到链表中

PyObject *PyDict_New(void){
	register dictobject *mp;
	//...
	mp = PyObject_GC_New(dictobject, &PyDict_Type);
	//...
	_PyObject_GC_TRACK(mp);//将创建的container对象链接到了Python中的可收集对象链表中。
	return (PyObject *)mp;
} 
#define _PyObject_GC_TRACK(0) do { 	PyGc_Head *g = _Py_AS_GC(0); 	if(g->gc.gc_refs != _PyObject_GC_UNTRACKED) 		Py_FatalError("GC object already tracked"); 	g->gc.gc_refs = _PyGC_REFS_REACHABLE; 	g->gc.gc_next = _PyGC_generations0;
	g->gc.gc_prev = _PyGC_generations0->gc.gc_prev; 	g->gc.gc_prev->gc.gc_prev = g;
	_PyGC_generations0->gc.gc_prev = g;	} while(0); 

#define _PyObject_GC_UNTRACK(0) do{
	//...
}

图16-13

struct gc_generation{
	PyGc_Head head;
	int threshold; //最多可容纳的container对象,超出这个值会立刻触发垃圾回收机制
	int count; //已经链接的container对象数目
}

Python中的标记-清除方法

在开始垃圾收集之前,Python会将“年轻”的代的内存链表整个地链接到count值越界的最“老”一代的内存链表之后。

图16-15

例子

图16-16

1.寻找Root Object集合

root object: 有可收集对象链表外部的某个引用在引用这个对象。图16-16中只有list1属于root object

引用计数副本--> PyGc_Head中的gc.gc_refs

利用PyGc_Head中的gc.gc_refs完成循环引用对象间环的摘除-->遍历container对象中的每一个引用,将它的引用计数减1

循环引用摘除后,PyGC_Head.gc.gc_refs不为0的container对象就是root object集合

图16-17

2.垃圾标记

两个链表

root 链表:root object和被root object直接或间接引用的对象

unreachable链表:垃圾对象

图16-18

3.垃圾收集

在寻找root object时,引入了gc_refs来模拟打破对象间循环引用的过程,现在要对ob_refcnt操作,直到unreachable链表中的

每一个对象的ob_refcnt变为0,引发对象的销毁。

分代收集

以空间换时间:将系统中的愉根据其存活时间划分为不同的集合,每一个集合就称为一个“代”,垃圾收集的频率随着“代”的存活时间的增大

而减小,也就是说,活得超长的对象,就越可能还是垃圾,就应该越少去收集。

Python中采用三代,一个代用一个链表维护。

《python源码剖析》笔记 pythonm内存管理机制,布布扣,bubuko.com

时间: 2024-10-05 04:34:02

《python源码剖析》笔记 pythonm内存管理机制的相关文章

Python源码剖析笔记3-Python执行原理初探

Python源码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源码剖析笔记,然而慢慢觉得没有从一个宏观的角度理解python执行原理的话,从底向上分析未免太容易让人疑惑,不如先从宏观上对python执行原理有了一个基本了解,再慢慢探究细节,这样也许会好很多.这也是最近这么久没有更新了笔记了,一直在看源码剖析书籍和源码,希望能够从一个宏观层面理清python执行原理.人说读书从薄读厚,再从厚读薄方是理解了

Python源码剖析笔记0 ——C语言基础

python源码剖析笔记0--C语言基础回顾 要分析python源码,C语言的基础不能少,特别是指针和结构体等知识.这篇文章先回顾C语言基础,方便后续代码的阅读. 1 关于ELF文件 linux中的C编译得到的目标文件和可执行文件都是ELF格式的,可执行文件中以segment来划分,目标文件中,我们是以section划分.一个segment包含一个或多个section,通过readelf命令可以看到完整的section和segment信息.看一个栗子: char pear[40]; static

python源码剖析笔记1——Python对象初见

python源码剖析笔记1--Python对象初见 工作整两年了,用python最多,然而对于python内部机制不一定都清楚,每天沉醉于增删改查的简单逻辑编写,实在耗神.很多东西不用就忘记了,比如C语言,正好,python源码用C写的,分析python源码的同时又能温故C语言基础,实在是件很好的事情.另外,还有陈儒大神的<python源码剖析>做指引,分析也不至于没头没脑.期望在一个月的业余时间,能有所小成,以此为记. 1 python中的对象 python中,一切东西都是对象,在c语言实现

Python源码剖析笔记4-内建数据类型

Python源码剖析笔记4-内建数据类型 Python内建数据类型包括整数对象PyIntObject,字符串对象PyStringObject,列表对象PyListObject以及字典对象PyDictObject等.整数对象之前已经分析过了,这一篇文章准备分析下余下几个对象,这次在<python源码剖析>中已经写的很详细的部分就不赘述了,主要是总结一些之前看书时疑惑的地方. 1 整数对象-PyIntObject 参见 python整数对象. 2 字符串对象-PyStringObject 2.1

Python源码剖析笔记2-Python整数对象

Python源码剖析笔记2-Python整数对象 本文简书地址: http://www.jianshu.com/p/0136ed90cd46 千里之行始于足下,从简单的类别开始分析,由浅入深也不至于自己丧失信心.先来看看Python整数对象,也就是python中的PyIntObject对象,对应的类型对象是PyInt_Type. 1 Python整数对象概览 为了性能考虑,python中对小整数有专门的缓存池,这样就不需要每次使用小整数对象时去用malloc分配内存以及free释放内存.pyth

Python源码剖析笔记6-函数机制

Python的函数机制是很重要的部分,很多时候用python写脚本,就是几个函数简单解决问题,不需要像java那样必须弄个class什么的. 本文简书地址:http://www.jianshu.com/p/d00108741a18 1 函数对象PyFunctionObject PyFunctionObject对象的定义如下: typedef struct { PyObject_HEAD PyObject *func_code; /* A code object */ PyObject *func

Python源码剖析笔记5-模块机制

本文简书地址: http://www.jianshu.com/p/14586ec50ab6 python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机制也是需要好好探究一下的,这次主要从黑盒角度来探测模块机制,源码分析点到为止,详尽的源码分析见陈儒大神的<python源码剖析>第14章. 1 如何导入模块 首先来看一个导入模块的例子.创建一个文件夹demo5,文件夹中有如下几个文件. [email protected] ~/demo5 $ ls

《python源码剖析》笔记 python虚拟机中的函数机制

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.Python虚拟机在执行函数调用时会动态地创建新的 PyFrameObject对象, 这些PyFrameObject对象之间会形成PyFrameObject对象链,模拟x86平台上运行时栈 2.PyFuctionObject对象 typedef struct { PyObject_HEAD PyObject *func_code: //对应函数编译后的PyCodeObject对象 Py

《python源码剖析》笔记 python中的整数对象

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1. PyIntObject --> long的一个简单包装 typedef struct{ PyObject_HEAD long ob_ival; } PyIntObject; PyInt_Type --> PyIntObject的类型对象.与对象相关的元信息实际上都是保存在与对象对应的类型对象中的 PyTypeObject PyInt_Type = { PyObject_HEAD_I