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行代码是不是挺奇怪的?你可能想问:为什么不是usedpoolssize呢?等下讲。这个明白了,498行代码也就明白了:为什么不是判断pool==NULL

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

下面解决第一个问题:usedpools

usedpools

usedpools的初始代码是很难理解的

在64位系统中,NB_SMALL_SIZE_CLASSES=32,宏展开以后长这样

第一个问题是:usedpools数组的所有值为什么不设置为NULL,而是给它赋初值PT(x)?

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

usedpools的所有值被称为dummy pool

第二个问题:usedpools2x与usedpools2x+1的值都是相等的,为什么?这也是一种代码设计策略

其中usedpools2x对应dummy pool的next,usedpools2x+1对应dummy pool的prev。你如何论证呢?看代码

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

第四个问题:usedpools2x为什么不直接指向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机制。关注公众号**【硬核子牙】**,看硬核文章

相关推荐
Draina12 小时前
CBC填充预言攻击-CBC Padding Oracle Crypto Attack
python·安全·web安全·网络安全·密码学·安全性测试
Angel Q.13 小时前
特征提取 | DINO 到 DINOv3
图像处理·python
不如语冰13 小时前
AI大模型入门-pytorch-张量2 tensor创建
python
NPE~13 小时前
[AI]Agent开发——ADK框架使用
人工智能·python·ai·教程·adk·agent开发
矮个史蒂芬13 小时前
统计verilog .v文件中output 的bit数
python
gwf21614 小时前
磨损均衡算法(Wear Leveling)——SSD如何让每块闪存“公平退休“?
运维·数据库·人工智能·python·嵌入式硬件·算法·智能硬件
爱吃提升14 小时前
python 分布式爬虫、爬虫合规与综合实战项目
分布式·爬虫·python
审小匠OpenCPAi14 小时前
银行流水核查怎么自动化?单边匹配、双向勾稽与图聚类异常检测的工程对比
java·前端·人工智能·python·审计
元Y亨H15 小时前
Pandas 解析 Excel 导致的内存溢出(MemoryError)
python·excel
金銀銅鐵16 小时前
[Python] 用 turtle 来绘制瑞典国旗
python