如何评价python的ios 内存管理机制制

Python源码阅读-内存管理机制(一)
==========================
基本阅读完了, 只是没时间梳理, 趁着这今天时间比较空
逐步梳理, 发上来......也算是小结下, 要开始准备简历找工作了&_&#
这篇略长, 带很多图, 所以一分为二
Python的内存管理架构
在Objects/obmalloc.c源码中, 给了一个分层划分
[ int ] [ dict ] [ list ] ... [ string ]
Python core
+3 | &----- Object-specific memory -----& | &-- Non-object memory --& |
_______________________________
Python's object allocator
+2 | ####### Object memory ####### | &------ Internal buffers ------& |
______________________________________________________________
Python's raw memory allocator (PyMem_ API)
+1 | &----- Python memory (under PyMem manager's control) ------& |
__________________________________________________________________
Underlying general-purpose allocator (ex: C library malloc)
0 | &------ Virtual memory allocated for the python process -------& |
=========================================================================
_______________________________________________________________________
OS-specific Virtual Memory Manager (VMM)
-1 | &--- Kernel dynamic storage allocation & management (page-based) ---& |
__________________________________
__________________________________
-2 | &-- Physical memory: ROM/RAM --& | | &-- Secondary storage (swap) --& |
layer 3: Object-specific memory(int/dict/list/string....)
Python 实现并维护
更高抽象层次的内存管理策略, 主要是各类特定对象的缓冲池机制. 具体见前面几篇涉及的内存分配机制
layer 2: Python's object allocator
Python 实现并维护
实现了创建/销毁Python对象的接口(PyObject_New/Del), 涉及对象参数/引用计数等
layer 1: Python's raw memory allocator (PyMem_ API)
Python 实现并维护, 包装了第0层的内存管理接口, 提供统一的raw memory管理接口
封装的原因: 不同操作系统 C 行为不一定一致, 保证可移植性, 相同语义相同行为
layer 0: Underlying general-purpose allocator (ex: C library malloc)
操作系统提供的内存管理接口, 由操作系统实现并管理, Python不能干涉这一层的行为
第三层layer 3前面已经介绍过了, 几乎每种常用的数据类型都伴有一套缓冲池机制.
在这里, 我们关注的是layer 2/1
简要介绍下layer 1, 然后重点关注layer 2, 这才是重点
layer 1: PyMem_ API
PyMem_ API是对操作系统内存管理接口进行的封装
查看pymem.h可以看到
// Raw memory interface
// 这里存在三个宏定义, 宏可以避免一次函数调用的开销, 提高运行效率
// 不允许非配空间大小为0的内存空间
#define PyMem_MALLOC(n)
((size_t)(n) & (size_t)PY_SSIZE_T_MAX ? NULL \
: malloc((n) ? (n) : 1))
#define PyMem_REALLOC(p, n) ((size_t)(n) & (size_t)PY_SSIZE_T_MAX
: realloc((p), (n) ? (n) : 1))
#define PyMem_FREE
// 这里做了三个函数的声明, 平台独立的 malloc/realloc/free
PyAPI_FUNC(void *) PyMem_Malloc(size_t);
PyAPI_FUNC(void *) PyMem_Realloc(void *, size_t);
PyAPI_FUNC(void) PyMem_Free(void *);
// ============================================================
// Type-oriented memory interface
// 这里还有三个类型相关的内存接口, 批量分配/重分配 n 个 类型为 type内存
#define PyMem_New(type, n) \
( ((size_t)(n) & PY_SSIZE_T_MAX / sizeof(type)) ? NULL :
( (type *) PyMem_Malloc((n) * sizeof(type)) ) )
#define PyMem_NEW(type, n) \
( ((size_t)(n) & PY_SSIZE_T_MAX / sizeof(type)) ? NULL :
( (type *) PyMem_MALLOC((n) * sizeof(type)) ) )
#define PyMem_Resize(p, type, n) \
( (p) = ((size_t)(n) & PY_SSIZE_T_MAX / sizeof(type)) ? NULL :
(type *) PyMem_Realloc((p), (n) * sizeof(type)) )
#define PyMem_RESIZE(p, type, n) \
( (p) = ((size_t)(n) & PY_SSIZE_T_MAX / sizeof(type)) ? NULL :
(type *) PyMem_REALLOC((p), (n) * sizeof(type)) )
然后object.c中, 我们关注实现, 三个实现的函数调用了对应的宏
// 使用 C 写Python扩展模块时使用函数而不是对应的宏
PyMem_Malloc(size_t nbytes)
return PyMem_MALLOC(nbytes);
PyMem_Realloc(void *p, size_t nbytes)
return PyMem_REALLOC(p, nbytes);
PyMem_Free(void *p)
PyMem_FREE(p);
这些接口都相对简单
好了, 结束, 开始关注layer 2: Python's object allocator
Python 的内存分配策略
先来看Objects/obmalloc.c中的一段注释
* "Memory management is where the rubber meets the road -- if we do the wrong
* thing at any level, the results will not be good. And if we don't make the
* levels work well together, we are in serious trouble." (1)
* (1) Paul R. Wilson, Mark S. Johnstone, Michael Neely, and David Boles,
"Dynamic Storage Allocation: A Survey and Critical Review",
in Proc. 1995 Int'l. Workshop on Memory Management, September 1995.
Python引入了内存池机制, 用于管理对小块内存的申请和释放
1. 如果要分配的内存空间大于 SMALL_REQUEST_THRESHOLD bytes(512 bytes), 将直接使用layer 1的内存分配接口进行分配
2. 否则, 使用不同的block来满足分配需求
整个小块内存池可以视为一个层次结构
1. 内存池(概念上的, 标识Python对于整个小块内存分配和释放的内存管理机制)
Python内存的最小单位, 所有block长度都是8字节对齐的
注意这里block只是一个概念, 在源代码中并没有实体存在.
不同类型block, 对应不同内存大小, 这个内存大小的值被称为size class.
不同长度的block
* Request in bytes
Size of allocated block
Size class idx
* ----------------------------------------------------------------
0, SMALL_REQUEST_THRESHOLD + 1 and up: routed to the underlying
allocator.
申请一块大小28字节的内存, 实际从内存中划到32字节的一个block (从size class index为3的pool里面划出)
注意: 这里有个Size class idx, 这个主要为了后面pool中用到
size class和size class index之间的转换
#define ALIGNMENT
/* must be 2^N */
#define ALIGNMENT_SHIFT
#define ALIGNMENT_MASK
(ALIGNMENT - 1)
// size class index =& size class
#define INDEX2SIZE(I) (((uint)(I) + 1) && ALIGNMENT_SHIFT)
(0+1) * 8 = 8
(1+1) * 8 = 16
// size class =& size class index
size = (uint)(nbytes - 1) && ALIGNMENT_SHIFT;
(8 - 1) / 8 = 0
(16 - 8) / 8 = 1
pool管理block, 一个pool管理着一堆有固定大小的内存块
本质: pool管理着一大块内存, 它有一定的策略, 将这块大的内存划分为多个大小一致的小块内存.
在Python中, 一个pool的大小通常为一个系统内存页. 4kB
obmalloc.c
#define SYSTEM_PAGE_SIZE
(4 * 1024)
#define SYSTEM_PAGE_SIZE_MASK
(SYSTEM_PAGE_SIZE - 1)
#define POOL_SIZE
SYSTEM_PAGE_SIZE
/* must be 2^N */
#define POOL_SIZE_MASK
SYSTEM_PAGE_SIZE_MASK
pool的4kB内存 = pool_header + block集合(N多大小一样的block)
pool_header
/* 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
*/ - size class index
uint nextoffset;
/* bytes to virgin block
uint maxnextoffset;
/* largest valid nextoffset
pool_header的作用
1. 与其他pool链接, 组成双向链表
2. 维护pool中可用的block, 单链表
3. 保存 szidx , 这个和该pool中block的大小有关系, (block size=8, szidx=0), (block size=16, szidx=1)...用于内存分配时匹配到拥有对应大小block的pool
4. arenaindex, 后面说
pool初始化
从内存中初始化一个全新的空的pool
Objects/obmalloc.c的
PyObject_Malloc(size_t nbytes)
init_pool:
// 1. 连接到 used_pools 双向链表, 作为表头
// 注意, 这里 usedpools[0] 保存着 block size = 8 的所有used_pools的表头
/* Frontlink to used pools. */
next = usedpools[size + size]; /* == prev */
pool-&nextpool = next;
pool-&prevpool = next;
next-&nextpool = pool;
next-&prevpool = pool;
pool-&ref.count = 1;
// 如果已经初始化过了...这里看初始化, 跳过
if (pool-&szidx == size) {
/* Luckily, this pool last contained blocks
* of the same size class, so its header
* and free list are already initialized.
bp = pool-&freeblock;
pool-&freeblock = *(block **)bp;
return (void *)bp;
* Initialize the pool header, set up the free list to
* contain just the second block, and return the first
// 开始初始化pool_header
// 这里 size = (uint)(nbytes - 1) && ALIGNMENT_SHIFT;
其实是Size class idx, 即szidx
pool-&szidx = size;
// 计算获得每个block的size
size = INDEX2SIZE(size);
// 注意 #define POOL_OVERHEAD
ROUNDUP(sizeof(struct pool_header))
// bp =& 初始化为pool + pool_header size,
跳过pool_header的内存
bp = (block *)pool + POOL_OVERHEAD;
// 计算偏移量, 这里的偏移量是绝对值
// #define POOL_SIZE
SYSTEM_PAGE_SIZE
/* must be 2^N */
// POOL_SIZE = 4kb, POOL_OVERHEAD = pool_header size
// 下一个偏移位置: pool_header size + 2 * size
pool-&nextoffset = POOL_OVERHEAD + (size && 1);
// 4kb - size
pool-&maxnextoffset = POOL_SIZE - size;
// freeblock指向 bp + size = pool_header size + size
pool-&freeblock = bp + size;
// 赋值NULL
*(block **)(pool-&freeblock) = NULL;
return (void *)bp;
初始化后的图
pool进行block分配 - 0 总体代码
总体分配的代码如下
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; // 指针指向空闲block起始位置
assert(bp != NULL);
// 调整 pool-&freeblock (假设A节点)指向链表下一个, 即bp首字节指向的下一个节点(假设B节点) , 如果此时!= NULL
// 表示 A节点可用, 直接返回
if ((pool-&freeblock = *(block **)bp) != NULL) {
return (void *)bp;
* Reached the end of the free list, try to extend it.
// 有足够的空间, 分配一个, pool-&freeblock 指向后移
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; // 注意, 指向NULL
return (void *)bp;
/* Pool is full, unlink from used pools. */
// 满了, 需要从下一个pool获取
next = pool-&nextpool;
pool = pool-&prevpool;
next-&prevpool = pool;
pool-&nextpool = next;
return (void *)bp;
pool进行block分配 - 1 刚开始
内存块尚未分配完, 且此时不存在回收的block, 全新进来的时候, 分配第一块block
(pool-&freeblock = *(block **)bp) == NULL
所以进入的逻辑是代码-2
bp = pool-&freeblock; // 指针指向空闲block起始位置
* Reached the end of the free list, try to extend it.
// 有足够的空间, 分配一个, pool-&freeblock 指向后移
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; // 注意, 指向NULL
return (void *)bp;
pool进行block分配 - 2 回收了某几个block
回收涉及的代码
PyObject_Free(void *p)
poolp pool;
block *lastfree;
poolp next, prev;
uint size;
pool = POOL_ADDR(p);
if (Py_ADDRESS_IN_RANGE(p, pool)) {
/* We allocated this address. */
/* Link p to the start of the pool's freeblock list.
* 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 */
// p被释放, p的第一个字节值被设置为当前freeblock的值
*(block **)p = lastfree = pool-&freeblock;
// freeblock被更新为指向p的首地址
pool-&freeblock = (block *)p;
// 相当于往list中头插入了一个节点
没释放一个block, 该block就会变成 pool-&freeblock 的头节点, 而单链表一个节点如何指向下一个节点呢? 通过赋值, 节点内存空间保存着下个节点的地址, 最后一个节点指向NULL(知道上面代码-1的判断条件了吧&_&#)
假设已经连续分配了5块, 第1块和第4块被释放
此时内存图示
此时再一个block分配调用进来, 执行分配, 进入的逻辑是代码-1
bp = pool-&freeblock; // 指针指向空闲block起始位置
// 调整 pool-&freeblock (假设A节点)指向链表下一个, 即bp首字节指向的下一个节点(假设B节点) , 如果此时!= NULL
// 表示 A节点可用, 直接返回
if ((pool-&freeblock = *(block **)bp) != NULL) {
return (void *)bp;
pool进行block分配 - 3 pool用完了
pool中内存空间都用完了, 进入代码-3
bp = pool-&freeblock; // 指针指向空闲block起始位置
/* Pool is full, unlink from used pools. */
// 满了, 需要从下一个pool获取
next = pool-&nextpool;
pool = pool-&prevpool;
next-&prevpool = pool;
pool-&nextpool = next;
return (void *)bp;
获取下一个pool(链表上每个pool的block size都是一致的)
好了, pool到此位置, 下篇进入arena
Copyright (C) 2015 wklken
Hosted on . Powered by . Social Icons by .Python 源码阅读:内存管理机制(2)
作者: wklken
Python 的内存分配策略
arena: 多个pool聚合的结果
arena size
pool的大小默认值位4KB
arena的大小默认值256KB, 能放置 256/4=64 个pool
obmalloc.c中代码
#define ARENA_SIZE (256 && 10) /* 256KB */
arena 结构
一个完整的arena = arena_object + pool集合
typedefuchar block;
/* Record keeping for arenas. */
structarena_object{
/* The address of the arena, as returned by malloc. Note that 0
* will never be returned by a successful malloc, and is used
* here to mark an arena_object that doesn't correspond to an
* allocated arena.
uptr address;
/* Pool-aligned pointer to the next pool to be carved off. */
block*pool_address;
/* The number of available pools in the arena: free pools + never-
* allocated pools.
uint nfreepools;
/* The total number of pools in the arena, whether or not available. */
uint ntotalpools;
/* Singly-linked list of available pools. */
// 单链表, 可用pool集合
structpool_header*freepools;
/* Whenever this arena_object is not associated with an allocated
* arena, the nextarena member is used to link all unassociated
* arena_objects in the singly-linked `unused_arena_objects` list.
* The prevarena member is unused in this case.
* When this arena_object is associated with an allocated arena
* with at least one available pool, both members are used in the
* doubly-linked `usable_arenas` list, which is maintained in
* increasing order of `nfreepools` values.
* Else this arena_object is associated with an allocated arena
* all of whose pools are in use. `nextarena` and `prevarena`
* are both meaningless in this case.
// arena链表
structarena_object*nextarena;
structarena_object*prevarena;
arena_object的作用
1.与其他arena连接,组成双向链表
2.维护arena中可用的pool,单链表
3.其他信息
pool_header 与 arena_object
pool_header和管理的blocks内存是一块连续的内存=& pool_header被申请时,其管理的block集合的内存一并被申请
arena_object和其管理的内存是分离的=& arena_object被申请时,其管理的pool集合的内存没有被申请,而是在某一时刻建立的联系
arena的两种状态
arena存在两种状态: 未使用(没有建立联系)/可用(建立了联系)
全局由两个链表维护着
/* The head of the singly-linked, NULL-terminated list of available
* arena_objects.
staticstructarena_object*unused_arena_objects= NULL;
/* The head of the doubly-linked, NULL-terminated at each end, list of
* arena_objects associated with arenas that have pools available.
// 双向链表
staticstructarena_object*usable_arenas= NULL;
arena的初始化
首先, 来看下初始化相关的一些参数定义
代码obmalloc.c
/* Array of objects used to track chunks of memory (arenas). */
// arena_object 数组
staticstructarena_object*arenas= NULL;
/* Number of slots currently allocated in the `arenas` vector. */
// 当前arenas中管理的arena_object的个数, 初始化时=0
staticuint maxarenas= 0;
/* How many arena_objects do we initially allocate?
* 16 = can allocate 16 arenas = 16 * ARENA_SIZE = 4MB before growing the
* `arenas` vector.
// 初始化时申请的arena_object个数
#define INITIAL_ARENA_OBJECTS 16
/* Number of arenas allocated that haven't been free()'d. */
staticsize_t narenas_currently_allocated= 0;
/* The head of the singly-linked, NULL-terminated list of available
* arena_objects.
// 未使用状态arena的单链表
staticstructarena_object*unused_arena_objects= NULL;
/* The head of the doubly-linked, NULL-terminated at each end, list of
* arena_objects associated with arenas that have pools available.
// 可用状态arena的双向链表
staticstructarena_object*usable_arenas= NULL;
然后, 看下obmalloc.c中arena初始化的代码
/* Allocate a new arena. If we run out of memory, return NULL. Else
* allocate a new arena, and return the address of an arena_object
* describing the new arena. It's expected that the caller will set
* `usable_arenas` to the return value.
staticstructarena_object*
new_arena(void)
structarena_object*arenaobj;
uint excess;/* number of bytes above pool alignment */
void*address;
// 判断是否需要扩充"未使用"的arena_object列表
if(unused_arena_objects== NULL){
uint numarenas;
size_t nbytes;
/* Double the number of arena objects on each allocation.
* Note that it's possible for `numarenas` to overflow.
// 确定需要申请的个数, 首次初始化, 16, 之后每次翻倍
numarenas= maxarenas?maxarenas1: INITIAL_ARENA_OBJECTS;
if(numarenas maxarenas)
returnNULL;/* overflow *///溢出了
nbytes= numarenas *sizeof(*arenas);
// 申请内存
arenaobj= (structarena_object *)realloc(arenas,nbytes);
if(arenaobj== NULL)
returnNULL;
arenas= arenaobj;
/* We might need to fix pointers that were copied. However,
* new_arena only gets called when all the pages in the
* previous arenas are full. Thus, there are *no* pointers
* into the old array. Thus, we don't have to worry about
* invalid pointers. Just to be sure, some asserts:
assert(usable_arenas== NULL);
assert(unused_arena_objects== NULL);
/* Put the new arenas on the unused_arena_objects list. */
for(i= maxarenas;inumarenas;++i){
arenas[i].address= 0;/* mark as unassociated */
// 新申请的一律为0, 标识着这个arena处于"未使用"
arenas[i].nextarena= inumarenas- 1?
&arenas[i+1]: NULL;
// 将其放入unused_arena_objects链表中
// unused_arena_objects 为新分配内存空间的开头
/* Update globals. */
unused_arena_objects= &arenas[maxarenas];
// 更新数量
maxarenas= numarenas;
/* Take the next available arena object off the head of the list. */
assert(unused_arena_objects!= NULL);
// 从unused_arena_objects中, 获取一个未使用的object
arenaobj= unused_arena_objects;
unused_arena_objects= arenaobj-&nextarena;// 更新链表
// 开始处理这个 arenaobject
assert(arenaobj-&address== 0);
// 申请内存, 256KB, 内存地址赋值给arena的address. 这块内存可用
#ifdef ARENAS_USE_MMAP
address= mmap(NULL,ARENA_SIZE,PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
err= (address== MAP_FAILED);
address= malloc(ARENA_SIZE);
err= (address== 0);
/* The allocation failed: return NULL after putting the
* arenaobj back.
arenaobj-&nextarena= unused_arena_objects;
unused_arena_objects= arenaobj;
returnNULL;
arenaobj-&address= (uptr)address;
++narenas_currently_allocated;
// 设置pool集合相关信息
arenaobj-&freepools= NULL;// 设置为NULL, 只有在释放一个pool的时候才有用
/* pool_address first pool-aligned address in the arena
nfreepools number of whole pools that fit after alignment */
arenaobj-&pool_address= (block*)arenaobj-&address;
arenaobj-&nfreepools= ARENA_SIZE/ POOL_SIZE;
assert(POOL_SIZE *arenaobj-&nfreepools== ARENA_SIZE);
// 将pool的起始地址调整为系统页的边界
// 申请到 256KB, 放弃了一些内存, 而将可使用的内存边界pool_address调整到了与系统页对齐
excess= (uint)(arenaobj-&address& POOL_SIZE_MASK);
if(excess!= 0){
--arenaobj-&nfreepools;
arenaobj-&pool_address+= POOL_SIZE- excess;
arenaobj-&ntotalpools= arenaobj-&nfreepools;
returnarenaobj;
图示: 初始化arenas数组, 初始化后的所有arena都在unused_arena_objects单链表里面
图示: 从arenas取一个arena进行初始化
没有可用的arena?
// 判断成立
if(unused_arena_objects== NULL){
// 确定需要申请的个数, 首次初始化, 16, 之后每次翻倍
numarenas= maxarenas?maxarenas&& 1: INITIAL_ARENA_OBJECTS;
然后, 假设第一次分配了16个, 发现没有arena之后, 第二次处理结果: numarenas = 32
即, 数组扩大了一倍
new了一个全新的 arena之后,
PyObject_Malloc(size_t nbytes)
// 刚开始没有可用的arena
if(usable_arenas== NULL){
// new一个, 作为双向链表的表头
usable_arenas= new_arena();
if(usable_arenas== NULL){
gotoredirect;
usable_arenas-&nextarena=
usable_arenas-&prevarena= NULL;
// 从arena中获取一个pool
pool= (poolp)usable_arenas-&pool_address;
assert((block*)pool&= (block*)usable_arenas-&address+
ARENA_SIZE- POOL_SIZE);
pool-&arenaindex= usable_arenas- arenas;
assert(&arenas[pool-&arenaindex]== usable_arenas);
pool-&szidx= DUMMY_SIZE_IDX;
// 更新 pool_address 向下一个节点
usable_arenas-&pool_address+= POOL_SIZE;
// 可用节点数量-1
--usable_arenas-&nfreepools;
图示: 从全新的arena中获取一个pool
假设arena是旧的, 怎么分配的pool
pool= usable_arenas-&freepools;
if(pool!= NULL){
这个arena-&freepools是何方神圣?
当arena中一整块pool被释放的时候
PyObject_Free(void*p)
structarena_object*ao;
uint nf;/* ao-&nfreepools */
/* Link the pool to freepools. This is a singly-linked
* list, and pool-&prevpool isn't used there.
ao= &arenas[pool-&arenaindex];
pool-&nextpool= ao-&freepools;
ao-&freepools= pool;
nf= ++ao-&nfreepools;
也就是说, 在pool整块被释放的时候, 会将pool加入到arena-&freepools作为单链表的表头, 然后, 在从非全新arena中分配pool时, 优先从arena-&freepools里面取, 如果取不到, 再从arena内存块里面获取
一个arena满了之后呢
很自然, 从下一个arena中获取
PyObject_Malloc(size_t nbytes)
// 当发现用完了最后一个pool!!!!!!!!!!!
// nfreepools = 0
if(usable_arenas-&nfreepools== 0){
assert(usable_arenas-&nextarena== NULL||
usable_arenas-&nextarena-&prevarena==
usable_arenas);
/* Unlink the arena: it is completely allocated. */
// 找到下一个节点!
usable_arenas= usable_arenas-&nextarena;
// 右下一个
if(usable_arenas!= NULL){
usable_arenas-&prevarena= NULL;// 更新下一个节点的prevarens
assert(usable_arenas-&address!= 0);
// 没有下一个, 此时 usable_arenas = NULL, 下次进行内存分配的时候, 就会从arenas数组中取一个
注意: 这里有个逻辑, 就是每分配一个pool, 就检查是不是用到了最后一个, 如果是, 需要变更usable_arenas到下一个可用的节点, 如果没有可用的, 那么下次进行内存分配的时候, 会判定从arenas数组中取一个
内存分配和回收最小单位是block, 当一个block被回收的时候, 可能触发pool被回收, pool被回收, 将会触发arena的回收机制
1.arena中所有pool都是闲置的(empty),将arena内存释放,返回给操作系统
2.如果arena中之前所有的pool都是占用的(used),现在释放了一个pool(empty),需要将arena加入到usable_arenas,会加入链表表头
3.如果arena中empty的pool个数n,则从useable_arenas开始寻找可以插入的位置.将arena插入.(useable_arenas是一个有序链表,按empty pool的个数,保证empty pool数量越多,被使用的几率越小,最终被整体释放的机会越大)
4.其他情况,不对arena进行处理
具体可以看PyObject_Free的代码
内存分配步骤
好的, 到这里, 我们已经知道了block和pool的关系(包括pool怎么管理block的), 以及arena和pool的关系(怎么从arena中拉到可用的pool)
那么, 在分析PyObject_Malloc(size_t nbytes)如何进行内存分配的时候, 我们就刨除掉这些管理代码
关注: 如何寻找得到一块可用的nbytes的block内存
其实代码那么多, 寻址得到对应的block也就这么几行代码, 其他代码都是pool没有, 找arena, 申请arena, arena没有, 找arenas, 最终的到一块pool, 初始化, 返回第一个block
如果有的情况, 用现成的
pool= usedpools[size+ size];
ifpool可用:
pool没满,取一个block返回
pool满了,从下一个pool取一个block返回
获取arena,从里面初始化一个pool,拿到第一个block,返回
从上面这个判断逻辑来看, 内存分配其实主要操作的是pool, 跟arena并不是基本的操作单元(只是用来管理pool的)
结论: 进行内存分配和销毁, 所有操作都是在pool上进行的
usedpools 是什么鬼? 其实是可用pool缓冲池, 后面说
arena 内存池的大小
取决于用户, Python提供的编译符号, 用于决定是否控制
obmalloc.c
#ifdef WITH_MEMORY_LIMITS
#ifndef SMALL_MEMORY_LIMIT
#define SMALL_MEMORY_LIMIT (64 * 1024 * 1024) /* 64 MB -- more? */
#ifdef WITH_MEMORY_LIMITS
#define MAX_ARENAS (SMALL_MEMORY_LIMIT / ARENA_SIZE)
具体使用中, python并不直接与arenas和arena打交道, 当Python申请内存时, 最基本的操作单元并不是arena, 而是pool
问题: pool中所有block的size一样, 但是在arena中, 每个pool的size都可能不一样, 那么最终这些pool是怎么维护的? 怎么根据大小找到需要的block所在的pool? =& usedpools
pool在内存池中的三种状态
1.used状态: pool中至少有一个block已经被使用,并且至少有一个block未被使用.这种状态的pool受控于Python内部维护的usedpool数组
2.full状态: pool中所有的block都已经被使用,这种状态的pool在arena中,但不在arena的freepools链表中
处于full的pool各自独立,不会被链表维护起来
3.empty状态: pool中所有block都未被使用,处于这个状态的pool的集合通过其pool_header中的nextpool构成一个链表,链表的表头是arena_object中的freepools
usedpools数组: 维护着所有处于used状态的pool, 当申请内存的时候, 会通过usedpools寻找到一块可用的(处于used状态的)pool, 从中分配一个block
#define SMALL_REQUEST_THRESHOLD 512
// 512/8 = 64
#define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT)
#define PTA(x) ((poolp )((uchar *)&(usedpools[2*(x)]) - 2*sizeof(block *)))
#define PT(x) PTA(x), PTA(x)
// 2 * ((64 + 7) / 8) * 8 = 128, 大小为128的数组
staticpoolp 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 */
// 得到usedpools数组
staticpoolp usedpools[128]= {
PTA(0),PTA(0),PTA(1),PTA(1),PTA(2),PTA(2),PTA(3),PTA(3),
PTA(63),PTA(63)
解开看(obmalloc.c)
typedefuchar block;
/* Pool for small blocks. */
structpool_header{
union{block *_padding;
uint count;}ref;/* number of allocated blocks */
block *freeblock;/* pool's free list head */
structpool_header *nextpool;/* next pool of this size class */
structpool_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 */
typedefstructpool_header *poolp;
usedpools[0]= PTA(0)= ((poolp)((uchar *)
为了看懂这步的trick, 心好累&_
new一个pool时维护
init获得的情况, 其实就是将刚刚从arena中获取的pool加入到 usedpools 对应的双向链表中, 然后初始化, 然后返回block
init_pool:
/* Frontlink to used pools. */
// 1. 获取得到usedpools链表头
next= usedpools[size+ size];/* == prev */
// 2. 将新的pool加入到双向链表
pool-&prevpool= next;
next-&nextpool= pool;
pool-&ref.count= 1;
// 3. 后面的是具体pool和block的了
if(pool-&szidx== size){
/* Luckily, this pool last contained blocks
* of the same size class, so its header
* and free list are already initialized.
bp= pool-&freeblock;
pool-&freeblock= *(block **)bp;
* Initialize the pool header, set up the free list to
* contain just the second block, and return the first
pool-&szidx= size;
size= INDEX2SIZE(size);
bp= (block *)pool+ POOL_OVERHEAD;
pool-&nextoffset= POOL_OVERHEAD+ (size maxnextoffset= POOL_SIZE- size;
pool-&freeblock= bp+ size;
*(block **)(pool-&freeblock)= NULL;
return(void*)bp;// here
从现有pool中获取block
从现有的pool, 其实就是 usedpools得到双向链表头部, 判断是不是空链表, 不是的话代表有可用的pool, 直接从里面获取
先这样吧, Python中整个内存池基本结构和机制大概如此, 是不是发现有好多数组/链表等等, 在分配/回收上处理下做成各种池…..
后面还有内存相关的就是垃圾收集了, 后面再说了吧
责任编辑:
声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
今日搜狐热点

我要回帖

更多关于 python 内存管理 很差 的文章

 

随机推荐