您的位置:首页 > 编程语言 > Python开发

对Python内存管理的认识(重点usedpool的一个trick的理解)

2013-02-24 04:01 281 查看
关于python内存的东西实在很多,这里只记录一些比较重要或者我认为值得注意的点。

在python中,内存管理机制被抽象成一种层次似得结果,如果所示



第0层是基于c语言的malloc,第1层主要是对第0层的内存管理接口进行包装,因为如果第0层操作系统不一样可能接口会不统一,第2层是对通用对象内存管理接口,第三层则是对int,string等常用对象进行内存管理接口的封装,里面做了不少优化工作。

前面的不少文章也都提到过内存池这个概念,不错,针对常用的Int,string,list这些类型,python都会有相应的内存池来提高效率。

整个小块内存的内存池可以视为一个层次结构,从小到大依次为block->pool->arena->内存池。

block和内存池在python源码里面并没有相对应的实体结构,只是一个概念,pool和arena则确实存在。

block是一个确定大小的内存块。block的长度都是8字节对齐的,可以有很多不同大小的block,比如8,16,24,32.。。。。。

pool是一组block的集合.一个pool通常为4KB(系统内存页大小).一个pool管理的所有block,它们的大小是一样的,不同的pool可以不一样。一个pool申请的时候会pool_header和相应的内存一起申请,而arena则不一样,它申请的时候可以理解为只申请了一个header,然后会在以后的某个步骤和pool产生联系,进行管理。

arena是一组pool的集合。arena通常为256KB,也就是一个arena含64个pool.arena对pool进行管理。arena分为“可用的”和“未用的”,通过链表来管理。具体实现细节下面的trick的地方会提到。值得注意的是,虽然arena管理pool,但是实际上python 的操作都是在pool上进行的。

毫无疑问,内存池就是由多个arena组成的。内存池有三种状态:used,full,empty.意思都是字面意思,很好理解。所有处于used状态下的pool都会被usedpools数组所管理。接下来就是比较trick的实现了,有点难理解,书上讲的不详细,另外网上查了资料,来说说我的理解。

下面是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是一个数组。这个数组是不同区间大小的used状态的内存池的头结点数组,但是类型不是pool_header,而是它的指针(这一点书上没有提及,不太容易理解),它只是指向头结点。



这就是不同区间大小的表现。0表示申请1-8的内存,1表示申请9-16的内存。。。。也就是说,数组里面每个指针指向的是一定大小的pool_header.

头结点的定义:

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      ""       */


假设我们要申请28的内存,换算下来应该是数组中表现3的位置,具体申请代码:

pool = usedpools[3+3];

if(pool != pool->nextpool)
{
//有可用的pool
}


trick的地方就在于为什么可以这么写?
#define PTA(x) ((poolp )((uchar *)&(usedpools[2*(x)]) - 2*sizeof(block *)))

这个宏说明数组里面存的元素是指向他们本身地址再向前2个指针大小的。而pool_header里面nextpool前面刚好有两个指针:_padding和freeblock,根据指针偏移,这样刚好是的数组元素地址对应nextpool。也就是如果将usedpool当中两个一组的指针看过是pool_header当中的nextpool跟prevpool域的时候,这段初始化代码正好是指回了”自身”,也就是初始化成为了一个空的带头结点双循环链表。换句话说就是初始化了一个节点node,node里面的一个指针指向了它自己(根据上面的指针偏移+数组来实现的)。所以这段代码的trick在于使用了两个指针大小就实现了pool_header的双循环链表头的功能,如果要访问大小区间索引为x的usedpool双链表,只需要这样的形式即可:usedpool[2*x],这就是它的头结点。注意这个头结点就真的只是用来构建双链表用的,所以_padding和freeblock两个指针被概念上的覆盖是没关系的(这点昨晚一直没想通,彻夜难眠=
=,果然还是太菜了)。所以如果pool这个头结点不等于pool->nextpool的话,就说明头结点后面还有节点,说明该nextpool已经被赋值过了,也就是说有可用的pool.

好了,上面就是我认为有点难理解的地方,如此写一遍也更加加深了我的理解。

最后上张图来看看几个层次之间的关系:



如果什么不对的地方,欢迎指出:)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐