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

Python的内存管理机制(一 小内存块的申请)

2016-11-23 23:38 267 查看
哈哈哈,看的有点儿兴奋了,笔记下:

      在Python的内部同时维护着宏和内存管理函数两种内存管理机制,宏定义可以节省一次函数调用的开销,

提高运行效率,但同时,使用宏是危险的,因为随着Python的演进,内存管理机制的实现可能会发生改变,因为宏的名字虽然是不会改变的,但其实现代码可能会发生改变,导致旧的宏编写的C扩展与新版本的Python产生二进制不兼容,这样十分危险,所以,在Python的内部维护了函数和宏两套接口,自己编写扩展时,最好使用函数

1 小块的内存池

在Python运行的过程中,很多时候都是小块内存的申请,申请后又很快释放,也就意味着大量的malloc和free

操作,导致操作系统频繁的在用户态和核心态之间进行切换,影响py的执行效率,为了解决这个问题Py引入了缓存池的机制(Pymalloc),整个小块内存的内存池可以视为一个层次结构,在这个层次可以分为4层,从上到下依次为:block , pool , arean和内存池

1.1 先看下block吧:

在最底层block是一个确定大小的内存,在py中有很多种block,每个block有不同的内存大小,这个内存的大小称为size class,可以不记得,记得这是最小单元就好,所有block事8字节对齐的,同时,py为block的大小设定了一个上限,当申请的内存大小小于这个上限时,py可以使用不同种类的block来满足需求,大于这个上限的时候,py转交给第一次的内存管理机制来处理,即pyMem函数族,在py2.5版本,这个值为256k

不同种类的block的大小分别为 8,16,32 ..... ,256,每个size class对应一个size class index ,这个index从0开始,假设申请内存为28,会得到一个32的block,能满足的最小值

1.2 那么超过256的内存申请怎么办呢 ? 来一起看下pool吧:

/* Pool for small blocks. */

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

};

       一组block的集合称为pool,也就是一个pool管理着多个有固定大小的block,一个pool的大小通常是一个内存页,py将其定义为4k,一个pool里边的block的大小是固定的,比如,可能管理了100个32k的block,也可能管理了100个64k的block,但是不可能一个pool里既有32k的block也有64k的block,每一个pool都和一个size联系到一起,更确切的说是和一个size class index联系到一起,这就是pool header里边的sindex的意义所在

一起看下pool是怎么把block组合到一起的:

/*

 * Initialize the pool header, set up the free list to

 * contain just the second block, and return the first

 * block.

 */

pool->szidx = size;

size = INDEX2SIZE(size);

bp = (block *)pool + POOL_OVERHEAD;

pool->nextoffset = POOL_OVERHEAD + (size << 1);

pool->maxnextoffset = POOL_SIZE - size;

pool->freeblock = bp + size;

*(block **)(pool->freeblock) = NULL;

UNLOCK();

return (void *)bp;

pool中的第一块内存是分配给pool_header的,所以pool的结构体中ref.count是1,第一块地址被分配后返回bp的是一个实际地址(也就是申请pool之后的返回),在这个地址之后会有进4k的内存都是实际可用的,但是可以肯定的是申请内存的函数只会使用[bp,bp+size]这个区间的内存,这是size class index保证的,

我该怎么画图呢 ? ......先跳过了

/*

 * There is a used pool for this size class.

 * Pick up the head block of its free list.

 */

++pool->ref.count;

bp = pool->freeblock;

assert(bp != NULL);

if ((pool->freeblock = *(block **)bp) != NULL) {

UNLOCK();

return (void *)bp;

}

/*

 * Reached the end of the free list, try to extend it.

 */

if (pool->nextoffset <= pool->maxnextoffset) {

/* There is room for another block. */

pool->freeblock = (block*)pool +

  pool->nextoffset;

pool->nextoffset += INDEX2SIZE(size);

*(block **)(pool->freeblock) = NULL;

UNLOCK();

return (void *)bp;

}

假设连续申请5块大小为28字节的内存,由于28字节的对应size class index为3,所以会申请5块32字节的内存,原来freeblock执行的是下一个可用bock的起始地址,再次申请是,只需要返回freeblock指向的地址就可

以了,很显然,freeblock需要前进,指向下一个可用block,此时nextoffset就发光发热了,在pool header里,nextoffset和maxoffset是对pool中的block步进迭代的变量,偏移地址恰好是下一个block的可用地址,在分配

完一个block之后,freeblock和nextoffset就移动一个block的距离,maxoffset则定义了最大的可用大小,划定了block的边界,nextoffset > maxoffset的时候,pool中的block就遍历完了,恩,就是这样的,申请->前进->

申请->前进......

但是假如一个block用完释放了,假如申请了连续5块32字节的内存,2用完释放了,下一个申请是用2还是6 ?

一旦py运行会有大量的这种内存碎片产生,py必须用一种机制把离散的内存管理起来,这就是自由的block链表,链表的关键就是header中的freeblock,回到上边,pool申请初始化完成之后,会指向一个有效地址,为下一个可分配的内存地址,还设置了一个*freeblock,等到有内存释放的时候,freeblock就会变成一个小精灵,

在这4k的内存上灵活舞动,哈哈~~

pool = POOL_ADDR(p);

if (Py_ADDRESS_IN_RANGE(p, pool)) {

/* We allocated this address. */

LOCK();

/* Link p to the start of the pool's freeblock list.  Since

 * the pool had at least the p block outstanding, the pool

 * wasn't empty (so it's already in a usedpools[] list, or

 * was full and is in no list -- it's not in the freeblocks

 * list in any case).

 */

assert(pool->ref.count > 0);/* else it was empty */

*(block **)p = lastfree = pool->freeblock;

pool->freeblock = (block *)p;

if (lastfree) {

struct arena_object* ao;

uint nf;  /* ao->nfreepools */

/* freeblock wasn't NULL, so the pool wasn't full,

 * and the pool is in a usedpools[] list.

 */

if (--pool->ref.count != 0) {

/* pool isn't empty:  leave it in usedpools */

UNLOCK();

return;

}

来看下,block释放的时候,freeblock指向一个有效的pool地址,但是*freeblock是NULL,假设释放的block是blockA,在pool->freeblock = (block *)p;的时候,freeblock的值被更新了,指向了blockA的首地址,在释放下一个block的时候,同样通过

*(block **)p = lastfree = pool->freeblock;

pool->freeblock = (block *)p;

把释放的block连接到一起,形成自由链表,在需要内存的时候可以很方便的遍历这个链表,优先使用被释放的内存,当pool->freeblock为NULL的时候就不存在离散的自由链表了,分配nextblock即可,nextoffset > maxoffset时,一个pool就用完了,怎么办 ?哈哈,莫着急,一定是存在一个pool的集合的,这就是下边要说的arean
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python 内存