zoukankan      html  css  js  c++  java
  • 《python源代码剖析》笔记 pythonm内存管理机制

    版权声明:本文为博主原创文章,未经博主同意不得转载。

    https://blog.csdn.net/zhsenl/article/details/37565519

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


    1.内存管理架构
    Python的内存管理机制都有两套实现:debug模式和release模式
    Python内存管理机制的层次结构:

    第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;



    申请内存时,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;
    		//...
    	}
    }


    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与其管理的内存则是分离的。


    当pool_header被申请时,它所管理的block集合的内存一定也被申请了;可是当 arena_object被申请时,
    它所管理的pool集合的内存则没有被申请。须要在某一时刻建立联系。
    当一个arena的arena_object没有与Pool集合建立联系时。这里的arena处于”未使用“状态,一旦建立了联系,
    这时的arena就转换到了”可用“状态。
    ”未使用“的arena链表表头是unused_arena_objects。是单向链表
    ”可用“的arena的链表表头是usable_arenas,是双向链表

    申请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的控制之下。

    //todo


    3.循环引用的垃圾收集
    Python 使用引用计数进行垃圾回收
    长处:实时性。不论什么内存,一旦没有指向它的引用,就会马上被回收。为了与引用计数机制搭配。在内存的分配与释放上获得最高的效率,
    Python设计了大量内存池机制。
    缺点:循环引用。使得一组对象的引用计数都不为0。

    -->解决方法:标记-清除,分代收集

    三色标记模型
    1.寻找root object(全局引用或函数栈中的引用)的集合
    2.垃圾检測:从root object 集合出发,沿着root object 集合中的每个引用,假设能到达某个对象A,则A称为可达的
    3.垃圾回收:保留可达对象,删除不可达对象


    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{
    	//...
    }



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

    Python中的标记-清除方法
    在開始垃圾收集之前,Python会将“年轻”的代的内存链表整个地链接到count值越界的最“老”一代的内存链表之后。

    样例

    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集合

    2.垃圾标记
    两个链表
    root 链表:root object和被root object直接或间接引用的对象
    unreachable链表:垃圾对象

    3.垃圾收集
    在寻找root object时,引入了gc_refs来模拟打破对象间循环引用的过程,如今要对ob_refcnt操作,直到unreachable链表中的
    每个对象的ob_refcnt变为0,引发对象的销毁。

    分代收集
    以空间换时间:将系统中的愉依据其存活时间划分为不同的集合,每个集合就称为一个“代”,垃圾收集的频率随着“代”的存活时间的增大
    而减小。也就是说,活得超长的对象,就越可能还是垃圾。就应该越少去收集。


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




  • 相关阅读:
    单循环判断数组中是否有存在重复值
    【Moss2010系列】利用BCS进行业务数据集成(1)
    状态压缩
    矩阵快速幂
    高精度加法
    旋转treap
    bitset
    快速幂
    splay
    考试注意
  • 原文地址:https://www.cnblogs.com/mqxnongmin/p/10961929.html
Copyright © 2011-2022 走看看