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机制。关注公众号**【硬核子牙】**,看硬核文章

相关推荐
cosX+sinY7 分钟前
10 卷积神经网络
python·深度学习·cnn
非极限码农16 分钟前
基于Deepseek的语言润色助手API实现与部署指南
python·微服务·自然语言处理
I love studying!!!26 分钟前
python基础:用户输入和 while 循环
python
AndrewHZ1 小时前
【图像处理基石】如何对遥感图像进行实例分割?
图像处理·人工智能·python·大模型·实例分割·detectron2·遥感图像分割
No0d1es2 小时前
第13届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2021年11月27日真题
python·青少年编程·蓝桥杯·选拔赛
天天找自己2 小时前
精通分类:解析Scikit-learn中的KNN、朴素贝叶斯与决策树(含随机森林)
python·决策树·机器学习·分类·scikit-learn
赵英英俊3 小时前
Python day31
开发语言·python
AI视觉网奇4 小时前
音频获取长度
java·前端·python
寄思~4 小时前
学习笔记:封装和单继承
开发语言·笔记·python·学习