当前位置:   article > 正文

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

pythoon 内存模型

本文为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的限定,可以得到以下结论:

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

Pool
一个pool管理着一堆有固定大小的内存块(block),这些block的大小都是一样的。
一个pool的大小通常是一个系统内存页,由于当前大多数Python支持的系统的内存页都是4KB,
所以Python内部也将一个pool的大小定义为4KB,这个大小包括pool_header和pool管理的blocks

  1. typedef uchar block;
  2. struct pool_header{
  3. union{ block *_padding;
  4. uint count;} ref; //count表示已经分配的block数目
  5. block *freeblock; //指向下一个可用 block
  6. struct pool_header *nextpool; //链接下一个pool
  7. struct pool_header *prevpool; //链接上一个pool
  8. uint arenaindex;
  9. uint szidx; //block 大小的index
  10. uint nextoffset; //指向 freeblock之后的下一个可用的block
  11. uint maxnextoffset; //指向了pool中最后一个可用的block距pool开始位置的偏移
  12. };


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

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



申请内存时,pool_header中各个域的变化
  1. if(pool != pool->nextpool){ ++pool->ref.count;
  2. bp = pool->freeblock;
  3. //...
  4. if(pool->nextoffset <= pool->maxnextoffset){
  5. //有足够的空间
  6. pool->freeblock = (block *) pool + pool->nextoffset;
  7. pool->nextoffset += INDEX2SIZE(size);
  8. *(block **)(pool->freeblock) = NULL; //建立离散自由block链表的关键所在
  9. return (void *)bp;
  10. }
  11. }

自由block链表
当一个block被释放的时候
  1. #define POOL_ADDR(P) ((poolp)((uptr)(p) & ~(uptr)POOL_SIZE_MASK))
  2. void PyObject_Free(void *p){
  3. poolp pool;
  4. block *lastfree;
  5. poolp next, prev;
  6. uint size;
  7. pool = POOL_ADDR(p);
  8. //判断p指向的block是否属于pool
  9. if(Py_ADDRESS_IN_RANGE(p, pool)){
  10. *(block **)p = lastfree = pool->freeblock;//[1]
  11. pool->freeblock = (block *)p;
  12. //...
  13. }
  14. }


Arena
一个Arena就是多个pool的集合,一个Arena的大小默认是256KB
  1. typedef uchar block;
  2. struct arena_object{
  3. uptr address;
  4. block *pool_address;
  5. uint nfreepools;
  6. uint ntotalpools;
  7. struct pool_header *freepools;
  8. struct arena_object *nextarena;
  9. struct arena_object *prevarena;
  10. }

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
  1. //arenas管理着arena_object的集合
  2. static struct arena_object *arenas = NULL;
  3. //当前arenas中管理的 arena_object的个数
  4. static uint maxarenas = 0;
  5. //"未使用"的 arena_object链表
  6. static struct arena_object *unused_arena_objects = NULL;
  7. //”可用“ 的 arena_object链表
  8. static struct arena_object *usable_arenas = NULL;
  9. //初始化时需主持 arena_object的个数
  10. #define INITIAL_ARENA_OBJECTS 16
  11. static struct arena_object *new_arena(void){
  12. struct arena_object *arenaobj;
  13. uint excess;
  14. //[1]:判断是否需要扩充”未使用“的 arena_object列表
  15. if(unused_arena_objects == NULL){
  16. uint i;
  17. uint numarenas;
  18. size_t nbytes;
  19. //[2]:确定本次需要申请的 arena_object的个数,并申请内存
  20. numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS;
  21. if(numarenas <= maxarenas)
  22. return NULL; //溢出
  23. nbytes = numarenas * sizeof(*arenas);
  24. if(nbytes / sizeof(*arenas) != numarenas)
  25. return NULL; //溢出
  26. arenaobj = (struct arena_object *)realloc(arenas, nbytes);
  27. if(arenaobj == NULL)
  28. return NULL;
  29. arenas = arenaobj;
  30. //[3]:初始化新申请的 arena_object,并将其放入 unused_arena_objects链表中
  31. for(i = maxarrenas; i < numarenas; ++i){
  32. arenas[i].address = 0; //mark as unassociated
  33. arenas[i].nextarena = i < numarenas - 1 ? &arenas[i + 1] : NULL;
  34. }
  35. //update globals
  36. unused_arena_objects = &arenas[maxarenas];
  37. maxarenas = numarenas;
  38. }
  39. //[4]:从 unused_arena_objects 链表中取出一个“未使用”的arena_object
  40. arenaobj = unused_arena_objects;
  41. unused_arena_objects = arenaobj->nextarena;
  42. assert(arenaobj->address == 0);
  43. //[5]:申请arena_object管理的内存
  44. arenaobj->address = (uptr)malloc(ARENA_SIZE);
  45. ++narenas_currently_allocated;
  46. //[6]:设置pool集合的相关信息
  47. arenaobj->freepools = NULL;
  48. arenaobj->pool_address = (block *)arenaobj->address;
  49. arenaobj->nfreepools = ARENA_SIZE / POOL_SIZE;
  50. //将pool的起始地址调整为系统页的边界
  51. excess = (uint)(arenaobj->address & POOL_SIZE_MASK);
  52. if(excess != 0){
  53. --arenaobj->nfreepools;
  54. arenaobj->pool_address += POOL_SIZE - excess;
  55. }
  56. arenaobj->ntotalpools = arenaobj->nfreepools;
  57. return arenaobj;
  58. }


内存池
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信息
  1. typedef union _gc_head{
  2. struct{
  3. union _gc_head *gc_next;
  4. union _gc_head *gc_prev;
  5. int gc_refs;
  6. } gc;
  7. long double dummy;
  8. } PyGc_Head;

在创建某个container的最后一步,将这个对象链接到链表中
  1. PyObject *PyDict_New(void){
  2. register dictobject *mp;
  3. //...
  4. mp = PyObject_GC_New(dictobject, &PyDict_Type);
  5. //...
  6. _PyObject_GC_TRACK(mp);//将创建的container对象链接到了Python中的可收集对象链表中。
  7. return (PyObject *)mp;
  8. }

  1. #define _PyObject_GC_TRACK(0) do { \
  2. PyGc_Head *g = _Py_AS_GC(0); \
  3. if(g->gc.gc_refs != _PyObject_GC_UNTRACKED) \
  4. Py_FatalError("GC object already tracked"); \
  5. g->gc.gc_refs = _PyGC_REFS_REACHABLE; \
  6. g->gc.gc_next = _PyGC_generations0;
  7. g->gc.gc_prev = _PyGC_generations0->gc.gc_prev; \
  8. g->gc.gc_prev->gc.gc_prev = g;
  9. _PyGC_generations0->gc.gc_prev = g;\
  10. } while(0);
  11. #define _PyObject_GC_UNTRACK(0) do{
  12. //...
  13. }



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

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中采用三代,一个代用一个链表维护。


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/199207
推荐阅读
相关标签
  

闽ICP备14008679号