大师兄的Python源码学习笔记(四十八): Python的内存管理机制(三)
大师兄的Python源码学习笔记(五十): Python的内存管理机制(五)
三、内存池
1. 可用pool缓冲池:usedpools
- 前面我们提到过,Python中的大块内存和小块内存分界点定在512个字节,这个分界点是由SMALL_REQUEST_THRESHOLD控制。
Objects/obmalloc.c
#define SMALL_REQUEST_THRESHOLD 512
- 当申请内存小于512个字节时,PyObject_Malloc会在内存池中申请内存。
- 当申请内存大于512个字节时,PyObject_Malloc的行为讲蜕化为malloc的行为。
- 当申请小于512字节的内存时,会使用arena所维护的内存空间,而arena的个数是否有限制,取决于用户。
- 当在WITH_MEMORY_LIMITS编译符号打开的背景下进行编译时,Python内部的另一个符号SMALL_MEMORY_LIMITS将被激活,它限制了可以创建的arena个数
/* * Maximum amount of memory managed by the allocator for small requests. */ #ifdef WITH_MEMORY_LIMITS #ifndef SMALL_MEMORY_LIMIT #define SMALL_MEMORY_LIMIT (64 * 1024 * 1024) /* 64 MB -- more? */ #endif #endif /* * The allocator sub-allocates <Big> blocks of memory (called arenas) aligned * on a page boundary. This is a reserved virtual address space for the * current process (obtained through a malloc()/mmap() call). In no way this * means that the memory arenas will be used entirely. A malloc(<Big>) is * usually an address range reservation for <Big> bytes, unless all pages within * this space are referenced subsequently. So malloc'ing big blocks and not * using them does not mean "wasting memory". It's an addressable range * wastage... * * Arenas are allocated with mmap() on systems supporting anonymous memory * mappings to reduce heap fragmentation. */ #define ARENA_SIZE (256 << 10) /* 256KB */ #ifdef WITH_MEMORY_LIMITS #define MAX_ARENAS (SMALL_MEMORY_LIMIT / ARENA_SIZE) #endif
- 在默认情况下,Win32平台和unix平台都会关闭WITH_MEMORY_LIMITS,所以通常Python没有对小块内存的内存池做任何限制。
- 尽管arena是小块内存池的最上层结构,但在实际使用中,Python的基本操作单元是pool。
- 这是因为pool是一个有size概念的内存管理抽象体,pool中的block总有确定的大小,并和size class index对应。
- 而arena是没有size概念的内存管理抽象体,同一个arena在某个时刻,其内的pool集合管理的block的大小可能根据系统需要而不同。
- 此外,内存池中的pool还是一个有状态的内存管理抽象体,一个pool在任何时刻,总是处于以下三种状态中的一种:
- used状态:pool中至少有一个block已经被使用,并且至少有一个block还未被使用,这种状态的pool受控于Python内部维护的usedpools数组。
- full状态:pool中所有的block都已经被使用,这种状态的pool在arena中,但不在arena的freepools链表中。
- empty状态:pool中所有的block都未被使用,处于这个状态的pool的集合通过其pool_header中的nextpool构成一个链表,这个链表头就是arena_object中的freepools。
- Python内部维护的usedpools数组巧妙的维护这所有处于used状态的pool,上图中所有处于used状态的pool都被置于usedpools的控制之下。
- 当申请内存时,Python会通过usedpools寻找一块处于used状态的pool,从中分配一个block。
- 这意味着一定有一个与usedpools相关联的机制,完成从申请的内存的大小到size class index之间的转换,观察usedpools的结构:
Objects/obmalloc.c
#define PTA(x) ((poolp )((uint8_t *)&(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)
#if NB_SMALL_SIZE_CLASSES > 8
, PT(8), PT(9), PT(10), PT(11), PT(12), PT(13), PT(14), PT(15)
#if NB_SMALL_SIZE_CLASSES > 16
, PT(16), PT(17), PT(18), PT(19), PT(20), PT(21), PT(22), PT(23)
#if NB_SMALL_SIZE_CLASSES > 24
, PT(24), PT(25), PT(26), PT(27), PT(28), PT(29), PT(30), PT(31)
#if NB_SMALL_SIZE_CLASSES > 32
, PT(32), PT(33), PT(34), PT(35), PT(36), PT(37), PT(38), PT(39)
#if NB_SMALL_SIZE_CLASSES > 40
, PT(40), PT(41), PT(42), PT(43), PT(44), PT(45), PT(46), PT(47)
#if NB_SMALL_SIZE_CLASSES > 48
, PT(48), PT(49), PT(50), PT(51), PT(52), PT(53), PT(54), PT(55)
#if NB_SMALL_SIZE_CLASSES > 56
, PT(56), PT(57), PT(58), PT(59), PT(60), PT(61), PT(62), PT(63)
#if NB_SMALL_SIZE_CLASSES > 64
#error "NB_SMALL_SIZE_CLASSES should be less than 64"
#endif /* NB_SMALL_SIZE_CLASSES > 64 */
#endif /* NB_SMALL_SIZE_CLASSES > 56 */
#endif /* NB_SMALL_SIZE_CLASSES > 48 */
#endif /* NB_SMALL_SIZE_CLASSES > 40 */
#endif /* NB_SMALL_SIZE_CLASSES > 32 */
#endif /* NB_SMALL_SIZE_CLASSES > 24 */
#endif /* NB_SMALL_SIZE_CLASSES > 16 */
#endif /* NB_SMALL_SIZE_CLASSES > 8 */
};
- 其中的NB_SMALL_SIZE_CLASSES指明了在当前的配置下,一共有多少个size class。
- 用一幅图解释usedpools结构:
- 假设申请28个字节时:
- Python会首先获得size class index,并通过
size = (uint )(nbytes - 1) >> ALIGNMENT_SHIFT
得到size class index为3。- 在usedpools中,寻找第3+3=6个元素,发现
usedpools[6]
的值指向usedpools[4]
的地址。- 因为
usedpools[6]->nextpool
指向的是usedpools[4]
(即usedpool+4)开始向后偏移8个字节(一个ref的大小加上一个freeblock的大小)的内存,也就是usedpools[4]
(即usedpool+4)的地址。
- 假设我们手中有一个size class为32字节的pool,想要放入到这个usedpools中时:
- 只需要进行
usedpools[i+i]->nextpool = pool
即可。- 其中i为size class index,对应32字节,所以i为3。
- 当下次需要访问size class为32字节的pool时,只需要简单地访问
usedpool[3+3]
就可以得到了。
- Python使用usedpools快速地从众多的pools迅速地寻找到一个最适合当前内存需求的pool,从中分配一块block。
- 在pymalloc_alloc中,Python利用了usedpools的巧妙结构,通过简单的判断来发现与某个class size index对应的pool是否在usedpools中存在:
Objects/obmalloc.c
static int
pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)
{
block *bp;
poolp pool;
poolp next;
uint size;
...
LOCK();
/*
* Most frequent paths first
*/
size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;
pool = usedpools[size + size];
if (pool != pool->nextpool) {
/*
* 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) {
goto success;
}
/*
* 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;
goto success;
}
/* Pool is full, unlink from used pools. */
next = pool->nextpool;
pool = pool->prevpool;
next->prevpool = pool;
pool->nextpool = next;
goto success;
}
... ...
}