此文是按照源码Python3.9来写,其中有些assert语句与一些不必要的宏字段会删除,保留核心的逻辑并添加注释,方便自己和大家理解。在代码中都会注明源码出处方便大家完整阅读。
为哈巴河等地区用户提供了全套网页设计制作服务,及哈巴河网站建设行业解决方案。主营业务为成都网站建设、网站设计、哈巴河网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
目录 | 概要 |
---|---|
Demo | 采用了Python的演示应用程序 |
Doc | 文档 |
Grammer | Python的语法文件 |
Include | 编译Python时引用的各种头文件 |
Lib | 标准附加库 |
Mac | Mac用的工具等 |
Misc | 很多文件的集合(如gdbinit和vimrc等) |
Modules | Python的C语言扩展模块 |
Objects | Python的对象用的C语言代码 |
PC | 依存于OS等环境的程序 |
PCbuild | 构造Win32和x64时使用 |
Parser | Python用的解析器 |
Python | Python的核心 |
Python是一门动态的、一切皆对象的语言,这些内存申请可能会产生大量小的内存,为了加快内存操作和减少内存碎片化,使用Python自己的内存管理器,叫PyMalloc。
- # Objects/obmalloc.c 代码注释
- /* An object allocator for Python.
- Here is an introduction to the layers of the Python memory architecture,
- showing where the object allocator is actually used (layer +2), It is
- called for every object allocation and deallocation (PyObject_New/Del),
- unless the object-specific allocators implement a proprietary allocation
- scheme (ex.: ints use a simple free list). This is also the place where
- the cyclic garbage collector operates selectively on container objects.
- Object-specific allocators
- _____ ______ ______ ________
- [ int ] [ dict ] [ list ] ... [ string ] Python core |
- +3 | <----- Object-specific memory -----> | <-- Non-object memory --> | # 对象特有的内存分配器
- _______________________________ | |
- [ Python's object allocator ] | |
- +2 | ####### Object memory ####### | <------ Internal buffers ------> | # Python对象分配器
- ______________________________________________________________ |
- [ Python's raw memory allocator (PyMem_ API) ] |
- +1 | <----- Python memory (under PyMem manager's control) ------> | | # Python低级内存分配器
- __________________________________________________________________
- [ Underlying general-purpose allocator (ex: C library malloc) ]
- 0 | <------ Virtual memory allocated for the python process -------> | # 通用的基础分配器(如glibc的malloc等)
- =========================================================================
- _______________________________________________________________________
- [ OS-specific Virtual Memory Manager (VMM) ]
- -1 | <--- Kernel dynamic storage allocation & management (page-based) ---> | # OS特有的虚拟内存管理器
- __________________________________ __________________________________
- [ ] [ ]
- -2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> | # 物理内存和交换目的地(如HDD等)
- */
- PyDict_New() // 第三层
- PyObject_GC_New() // 第二层
- PyObject_Malloc() // 第二层
- new_arena() // 第一层
- malloc() // 第零层
- ////////////////////////////////////////以下2层属于操作系统范畴,不在讨论范围/////////////////////////////////
图1
- //Objects/obmalloc.c
- #define SMALL_REQUEST_THRESHOLD 512
- #define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT)
- /* Largest positive value of type Py_ssize_t. */
- #define PY_SSIZE_T_MAX ((Py_ssize_t)(((size_t)-1)>>1))
- static void *
- _PyObject_Malloc(void *ctx, size_t nbytes)
- { // 走Python的分配器,函数进去就会有判断(0,512]的才使用
- void* ptr = pymalloc_alloc(ctx, nbytes);
- if (LIKELY(ptr != NULL)) {
- return ptr;
- }
- // 大于512字节走C的malloc,函数进去进做了越界判断,Py_ssize_t为阈值
- ptr = PyMem_RawMalloc(nbytes);
- if (ptr != NULL) {
- raw_allocated_blocks++;
- }
- return ptr;
- }
在源代码中以PyMem_为前缀的所有函数是封装C语言提供给Python语法使用的,其核心使用的就是第0层malloc之类的C库函数。
当Python在WITH_MEMORY_LIMITS编译符号打开的背景下进行编译时,Python内部的另一个符号会被激活,这个名为SMALL_MEMORY_LIMIT的符号限制了整个内存池的大小,同时,也就限制了可以创建的arena的个数。
在默认情况下,不论是Win32平台,还是unix平台,这个编译符号都是没有打开的,所以通常Python都没有对小块内存的内存池的大小做任何的限制。
- [obmalloc.c]
- #ifdef WITH_MEMORY_LIMITS
- #ifndef SMALL_MEMORY_LIMIT
- #define SMALL_MEMORY_LIMIT (64 * 1024 * 1024) /* 64 MB -- more? */
- #endif
- #endif
- #ifdef WITH_MEMORY_LIMITS
- #define MAX_ARENAS (SMALL_MEMORY_LIMIT / ARENA_SIZE)
- #endif
有了以下的宏定义,我们写代码的时候只需要提供类型和数量,而不用自己去计算具体需要申请多少空间
- //Include/pymem.h
- #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)) )
- #define PyMem_Del PyMem_Free
- #define PyMem_DEL PyMem_FREE
每次申请内存的时候一定不会每次都遇到刚好的块去分配,那么一下一大块内存会被切割使用,那么中间会产生很多小的但是可能不在会被使用的碎片(但是整个加起来也是一个大的可使用的块),而且每次查找合适的块需要遍历整个堆,所以为了减少碎片和快速分配内存,我们需要内存管理。
图2
小于512字节的内存申请由Python的低级分配器接管(空白内存,raw memory),做了3级层次的划分,依次为block、pool、arena
图3
pool与arena头与boby连接的不同
图4
现在来到的是真正Python的内存管理谈论的部分了,Python内存管理做了哪些处理
arena
“ 第一层的核心就是创建arena
arena的大小
arena的默认值是256K
- #define ARENA_BITS 18 /* 256 KiB */
- #define ARENA_SIZE (1 << ARENA_BITS)
- // Objects/obmalloc.c
- struct arena_object {
- // arena_object地址
- uintptr_t address;
- // 将arena的地址用于给pool使用而对齐的地址
- block* pool_address;
- // 该arena中可用pool的数量
- uint nfreepools;
- // 该arena中所有pool的数量
- uint ntotalpools;
- // 使用完毕的pool,用单链表维护
- struct pool_header* freepools;
- // 双向链表指针
- struct arena_object* nextarena;
- struct arena_object* prevarena;
- };
为什么arena_object需要address和pool_address2个字段?
“ 上面内存管理的划分提到arena_object与body是不连续的,图4
pool_header被申请时,它所管理的block集合的内存一定也被申请了;所以他是连续的一块空间
但是当aerna_object被申请时,它所管理的pool集合的内存则没有被申请;arena需要指针相连
所以address指定的是头数据,pool_address指定的是真实数据开始的位置,所以不同
new_arena
类型
“ uintptr_t 是由从 C99 开始导入的 stdint.h 提供的,在将 C 指针转化成整数时,它起着很大的作用。uintptr_t 正是负责填补这种环境差异的。uintptr_t 会根据环境变换成 4 字节或 8 字节,将指针安全地转化,避免发生溢出的问题。
- // uchar 和 uint 分别是 unsigned ××× 的略称。
- #undef uchar
- #define uchar unsigned char /* 约8位 */
- #undef uint
- #define uint unsigned int /* 约大于等于16位 */
- #undef ulong
- #define ulong unsigned long /* 约大于等于32位 */
- #undef uptr
- #define uptr Py_uintptr_t
- typedef uchar block;
- //[obmalloc.c]
- // arenas管理着arena_object的集合
- static struct arena_object* arenas = NULL;
- // 当前arenas中管理的arena_object的个数
- static uint maxarenas = 0;
- // “未使用的”arena_objectd单向链表
- static struct arena_object* unused_arena_objects = NULL;
- // “可用的”arena_object链表
- static struct arena_object* usable_arenas = NULL;
- // 初始化时需要申请的arena_object的个数
- #define INITIAL_ARENA_OBJECTS 16
- //[obmalloc.c]
- static struct arena_object*
- new_arena(void)
- {
- struct arena_object* arenaobj;
- uint excess; /* number of bytes above pool alignment */
- // 初始化默认值为NULL,需要生成arena_objects
- if (unused_arena_objects == NULL) {
- uint i;
- uint numarenas;
- size_t nbytes;
- // 确定申请arena的个数,初始化得到16个,之后会2倍扩容
- numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS;
- // 溢出判断
- if (numarenas <= maxarenas)
- return NULL;
- nbytes = numarenas * sizeof(*arenas);
- if (nbytes / sizeof(*arenas) != numarenas)
- return NULL;
- // 需要使用0层的分配器分配numarenas个数arena_object(头信息)所需的raw memory
- // 分配完后arenas作为静态全局变量
- arenaobj = (struct arena_object *)realloc(arenas, nbytes);
- if (arenaobj == NULL)
- return NULL;
- arenas = arenaobj;
- // 把以上分配的raw memory,维护到unused_arena_objects单向链表中
- for (i = maxarenas; i < numarenas; ++i) {
- // arena地址,如果没有分配就用0作为标识符
- arenas[i].address = 0;
- // 最后一个arena指向NULL,其余都指向下一个指针,初始化分配是一个连续的单链表
- arenas[i].nextarena = i < numarenas - 1 ? &arenas[i+1] : NULL;
- }
- /* 反映到全局变量中 */
- unused_arena_objects = &arenas[maxarenas];
- maxarenas = numarenas;
- }
- ////////////////////////////////////以上完成了arenas 的初始化,如下图所示//////////////////////////////////////////
- // 从unused_arena_objects链表中取出一个“未使用的”arena_object(表头)
- arenaobj = unused_arena_objects;
- unused_arena_objects = arenaobj->nextarena;
- assert(arenaobj->address == 0);
- // 分配一块arena内存,256KB
- // 这时候address有具体地址了
- arenaobj->address = (uptr)malloc(ARENA_SIZE);
- ++narenas_currently_allocated;
- if (arenaobj->address == 0) {
- // 分配失败,让把拿出来的头放回到unused_arena_objects链表中
- arenaobj->nextarena = unused_arena_objects;
- unused_arena_objects = arenaobj;
- return NULL;
- }
- ///////////////////////////////以上是分配arena空间与arena_object连接///////////////////////////////////////
- // 将arena内的空间分割为各个pool
- arenaobj->freepools = NULL;
- /* pool_address 对齐后开头pool的地址
- nfreepools 对齐后arena中pool的数量 */
- arenaobj->pool_address = (block*)arenaobj->address;
- arenaobj->nfreepools = ARENA_SIZE / POOL_SIZE;
- // 内存对齐
- excess = (uint)(arenaobj->address & POOL_SIZE_MASK);
- if (excess != 0) {
- --arenaobj->nfreepools;
- arenaobj->pool_address += POOL_SIZE - excess;
- }
- arenaobj->ntotalpools = arenaobj->nfreepools;
- return arenaobj;
- }
- /////////////////////////////////////////以上是划分pool/////////////////////////////////////////////////////
1、初始化16个arena_object
图5
2、扩容
图6
3、分配arena空间,就是arena表头与真实数据相连
图7
4、给arena划分pool,excess是什么-内存对齐会消耗一个pool
结构体 arena_object 的成员 pool_address 中存有以 4K 字节对齐的 pool 的地址。
在此使用 POOL_SIZE_MASK 来对用 malloc() 保留的 arena 的地址进行屏蔽处理,计算超过的量(excess)。
如果超过的量(excess)为 0,因为 arena 的地址刚好是 4K 字节(2 的 12 次方)的倍数,所以程序会原样返回分配的 arena_object。这时候因为 arena 内已经被 pool 填满了,所以可以通过计算 arena 的大小或 pool 的大小来求出 arena 内 pool 的数量。
如果超过的量不为 0,程序就会计算“arena 的地址 + 超过的量”,将其设置为成员pool_address。此时 arena 内前后加起来会产生一个 pool 的空白,nfreepools--。
图8
“ arena_object是否与pool建立联系导致状态不同
unused_arena_object(未使用状态)
usable_arenas(可用状态)
图9
“ 第 2 层的分配器负责管理 pool 内的 block。这一层实际上是将 block 的开头地址返回给申请者,并释放 block 等。
一个pool被分割成一个个的block。Python中生成对象时,最终都会被分一个或几个block上。block是Python内存分配的最小单元
大小以8个字节为梯度的内存块,就是类保证内存对齐(字对齐)
1、提高了CPU的读写速度
2、减少了碎片大小(必不可少的浪费)
- // 以下的宏
- // 索引为0的话, 就是1 << 3, 显然结果为8
- // 索引为1的话, 就是2 << 3, 显然结果为16
- #define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT)
- * Request in bytes Size of allocated block Size class idx
- * ----------------------------------------------------------------
- * 1-8 8 0
- * 9-16 16 1
- * 17-24 24 2
- * 25-32 32 3
- * 33-40 40 4
- * 41-48 48 5
- * 49-56 56 6
- * 57-64 64 7
- * 65-72 72 8
- * ... ... ...
- * 497-504 504 62
- * 505-512 512 63
所以当我们需要申请44个字节的内存空间的时候,PyObject_Malloc会从内存池中划分一个 48 字节的block使用
- //Objects/obmalloc.c
- #define ALIGNMENT 8 /* must be 2^N */
- #define ALIGNMENT_SHIFT 3
“ 我们可以从图8里看到excess是为了在arena中pool4K大小的对齐,所以block以8字节的倍数自然都是对齐的
由于pool_header中szidx确定
图10
CPU 原则上能从对齐的地址取出数据。相应地,malloc() 分配的地址也应配合 CPU 对齐来返回数据。
利用这一点的著名 hack 就是将地址的低 3 位用作标志。
假设在结构体内存入某个指针。如果从 malloc() 返回的地址是按 8 字节对齐的,那么其指针的低 3 位肯定为“0”。于是我们想到了在这里设置位,将其作为标志来使用。当我们真的要访问这个指针时,就将低 3 位设为 0,无视标志。
这是一个非常大胆的 hack,但事实上 glibc malloc 却实现了这个 hack。
block 有3种状态管理
未使用:未使用自然没有链表的指向了,那么我们只能在pool_head上设置第一个可以使用块的偏移量nextoffset
图11
pool是与系统页一样的4KB的大小,其中一个pool只能管理一个种规格的block,由szidx字段来标识。所以pool是具有size概念的block集合
- //Objects/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
在讲解arena初始化的时候第4部分讲到了excess就是为了做pool的内存对齐,可见图8。这里就不在赘述
一个pool的头由48个字节组成,所有的pool以双向链表的形式连接
- //Objects/obmalloc.c
- /* When you say memory, my mind reasons in terms of (pointers to) blocks */
- typedef uint8_t block;
- /* Pool for small blocks. */
- struct pool_header {
- union { block *_padding;
- uint count; } ref; /* 当前pool里面已分配出去的block数量 */
- block *freeblock; /* 指向空闲block链表的第一块 */
- struct pool_header *nextpool; /* next和prev提供usedpool使用,减少缓存表的空间 */
- struct pool_header *prevpool;
- uint arenaindex; /* 自己所属的arena的索引(对于arenas而言) */
- uint szidx; /* 分配的block的大小,所以pool中的所有块大小一致 */
- uint nextoffset; /* 下一个可用block的内存偏移量 */
- uint maxnextoffset; /* 最后一个block距离开始位置的偏移量 */
- };
- typedef struct pool_header *poolp;
图12
图13
“ 作用就是管理所有used状态的pool
- // poolp大概是pool_header的指针型的别名。也就是说,usedpools 是 pool_header 的指针型的数组。
- typedef struct pool_header *poolp;
宏 NB_SMALL_SIZE_CLASSES
- #define ALIGNMENT 8 /* 有必要为2的N次方 */
- #define SMALL_REQUEST_THRESHOLD 512
- // 指明了在当前的配置之下,一共有多少个size class。
- #define NB_SMALL_SIZE_CLASSES (SMALL_REQUEST_THRESHOLD / ALIGNMENT)
usedpools的初始化大小
- // 这个宏定义了一个指针,这个指针指向的位置是从一组的开头再往前“两个 block 指针型的大小”。
- #define PTA(x) ((poolp )((uint8_t *)&(usedpools[2*(x)]) - 2*sizeof(block *)))
- // 宏 PT() 以两个一组的形式调用宏 PTA()。
- #define PT(x) PTA(x), PTA(x)
- // usedpools数组有128个
- 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 */
- };
现在以为usedpool的角度出发来看
图14
used就是把使用了至少一个块,但是还没有全部使用完的pool整合到一个usedpool中,那么这一个做法类似以hash表的链地址法,通过下标可以O(1)到达同一size的usedpool[下标]的位置,然后使用链表,因为empty->used和used->full,方便插入和删除pool
一个例子
1、当申请20个字节内存的时候,Python会首先获得size class index,通过size = (uint )(nbytes \- 1) >> ALIGNMENT_SHIFT,其中ALIGNMENT_SHIFT是内存对齐的需要右移3位(即8字节对齐),得到(20-1)>>3=2
2、通过usedpools[i+i]->nextpool可以快速找到一个最合适当前内存需求的pool
- byte = 20 /* 申请的字节数*/
- byte = (20 - 1) >> 3 /* 对齐:结果 2 */
- pool = usedpools[byte+byte] /* 因为是两两一组,所以索引加倍: index 4 */ // O(1)
- // 这时,取出的 pool 存在如下关系。
- pool; == pool->nextpool
- pool; == pool->prevpool
- pool->nextpool == pool->prevpool // O(1)
在需要缓存的时候,能够尽可能地让缓存少承载一些引用表。(只需要pool_header中两个内部的指针成员,next和prev)
如果直接保留 pool_header 的话,往往就会出现 usedpools 变得太大,缓存承载不下的状况。因为我们要频繁引用数组 usedpools,所以让它小一些才会减轻缓存的压力。
通过尽量不使用那些可用空间多的内存空间,增加了使其完全变为空的机会。如果这部分内存空间完全为空,那么就能将其释放。
在释放的时候从pymalloc_free函数观察来看,是头插放在usedpool[奇数],full状态变为used状态
- // free中的代码,
- if (UNLIKELY(lastfree == NULL)) {
- uint size = pool->szidx;
- poolp next = usedpools[size + size]; // 双向链表的尾部
- poolp prev = next->prevpool;
- pool->nextnextpool = next;
- pool->prevprevpool = prev;
- next->prevpool = pool; <
本文题目:Python内存管理介绍
网页路径:http://www.shufengxianlan.com/qtweb/news17/83817.html网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联