Python虚拟机内存机制底层

我,子牙老师,一个手写过操作系统、编程语言、Java虚拟机、docker、Ubuntu系统…的硬核男人

最近抽空研究了CPython的内存机制,写篇文章记录一下。看完本篇文章,Python虚拟机CPython内存管理机制的源码,你就能轻松看懂

本篇文章,你会看到:

  1. CPython的内存分配域
  2. pymalloc中的arena、pool、block之间的关系
  3. arena、pool的设计细节
  4. usedpools的设计细节及N问题解答
  5. 创建对象时,CPython是如何从pymalloc中分配内存的

以下,enjoy

内存分配域

CPython中定义了三种内存分配域(domain)来支持不同用途的内存管理机制
在这里插入图片描述

虽然定义了三个内存分配域,事实上只实现了两个:Raw Domain、Object Domain。Mem Domain使用的也是Object Domain

如果不开启WITH_PYMALLOC,那三个内存分配域,使用的就是同一个:Raw Domain
在这里插入图片描述

WITH_PYMALLOC的用途:控制是否启用 Python 自带的小对象内存池分配器pymalloc

这个配置默认是开启的,如果你想查看
在这里插入图片描述

如何禁用呢?
在这里插入图片描述

Object Domain与pymalloc是什么关系呢?Object Domain专用于 Python 对象,由 pymalloc 管理,分配小对象。那pymalloc底层是如何玩的呢?

pymalloc

pymalloc底层是通过三个玩意来管理的:arena、pool、block。理解了这三者之间的关系,你就把pymalloc玩明白了。它们的关系如图
在这里插入图片描述

arena,一块256K的内存,由arena_object管理,属性address指向这块内存区域

初始的时候,pool_address的值与address相同,但是address是固定不变的,pool_address会随着arena中的内存使用而改变。比如最开始是指向第一个pool,第一个pool被使用了,就会指向第二个pool,言外之意就是它永远指向下一个可用的pool

一个pool,4K。所以一个arena,有64个pool。那一个pool中有多少block呢?看情况

64位系统中,block大小从16B开始,步长也是16,终点是512B,一共是32种:16、32、48、64…512

所以一个block,如果是16B,那就是4096/16=256个。真的是这样吗?不是!因为一个pool,4K内存,它的头部会放这个pool的管理数据,即pool_header,占48B,所以结果是(4096-48)/16=253

虽然启用了pymalloc,但是超过512B的内存申请不会走pymalloc,还是会走Raw Domain。即使用c库函数malloc、calloc向系统申请内存
在这里插入图片描述

我现在讲的这些都是后面看懂代码的关键,所以没看懂的多看几遍

这三者之间的大体关系差不多就是这样,接下来举个例子帮助大家彻底理解

pymalloc_alloc

当我们需要分配内存的时候,整个调用链是这样
在这里插入图片描述

看下pymalloc_alloc的源码
在这里插入图片描述

第一个判断:如果要分配的内存大小是0,返回NULL

第二个判断:如果分配的内存大小超过512B,返回NULL,就回到调用函数中,走RawMalloc,向OS要内存
在这里插入图片描述

再将nbytes转成size class,即图中的i,去usedpools中找到对应的pool,从中分配内存
在这里插入图片描述

如果要分配的内存是1B-16B,对应的size class=0;如果要分配的内存是17B-32B,对应的size class=1……

494行代码是不是挺奇怪的?你可能想问:为什么不是usedpools[size]呢?等下讲。这个明白了,498行代码也就明白了:为什么不是判断pool==NULL

第一次分配内存,会走到516行,函数allocate_from_new_pool里面干了什么,等下讲

下面解决第一个问题:usedpools

usedpools

usedpools的初始代码是很难理解的
在这里插入图片描述

在64位系统中,NB_SMALL_SIZE_CLASSES=32,宏展开以后长这样
在这里插入图片描述

第一个问题是:usedpools数组的所有值为什么不设置为NULL,而是给它赋初值PT(x)?
在这里插入图片描述

答案:为了实现环形哑链表。这个链表比起普通的链表有什么好处?不需要判断空指针
在这里插入图片描述

usedpools的所有值被称为dummy pool

第二个问题:usedpools[2x]与usedpools[2x+1]的值都是相等的,为什么?这也是一种代码设计策略
在这里插入图片描述

其中usedpools[2x]对应dummy pool的next,usedpools[2x+1]对应dummy pool的prev。你如何论证呢?看代码
在这里插入图片描述

第三个问题:- 2*sizeof(block *),在64位系统中相当于-16,next在pool_header中的offset是16,这个没错,但是prev在pool_header中的offset是24,为什么也是-16?因为初始的时候只要保持非NULL即可

第四个问题:usedpools[2x]为什么不直接指向dummy pool,而是dummy pool的next?ChatGPT这样说
在这里插入图片描述
在这里插入图片描述

至此,usedpools你就算玩明白了

allocate_from_new_pool

理解了上面这些,你就能轻松看懂CPython内存管理机制相关源码,不信咱们来试试
在这里插入图片描述

刚开始,usable_arenas中还没有数据,就调用new_arena创建,初始创建16个,后面每次double倍创建
在这里插入图片描述

从arena中拿一个pool出来用,并做好相关的值设置。如果这个pool是arena中的最后一个,就切换到下一个arena待用
在这里插入图片描述

完成pool的初始化
在这里插入图片描述

头插法,将pool插入usedpools对应的size class链表中
在这里插入图片描述

如果usedpools中有对应的size class对应的pool,直接拿出来用
在这里插入图片描述

如果usedpools中没有对应的size class对应的pool,从前面拿出来用的pool中分配block并返回

这里注意一点:struct arena_object与256K内存是分配的,但是struct pool_header是包含在4K内存之中的,所以一个pool真正可用的内存大小是4K-sizeof(pool)

至此,Python虚拟机CPython的内存管理机制,你就算过关了。下一篇,聊Python虚拟机的GC机制。关注公众号**【硬核子牙】**,看硬核文章。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值