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

相关推荐
沉下心来学鲁班1 小时前
DeepAgents 记忆机制:让 AI Agent 拥有“跨对话“的记忆
服务器·人工智能·python
Helix2501 小时前
Python与其他编程语言优劣势对比:2026初学者选型指南
java·python·教程·编程语言·入门·初学者编程选型
零基础1231 小时前
深度强化学习驱动的 Agent 后训练:原理、实践与前沿路线
人工智能·python·机器学习
I Am a robert girl1 小时前
PhysStream 源码级拆解:流式视频生成如何做到“边生成边控制“
python·音视频·源码解析·视频生成·可控生成·流式生成·物理控制
高洁011 小时前
数字孪生驱动大模型工业知识库
人工智能·python·深度学习·机器学习·transformer
RS迷途小书童2 小时前
Python+FFmpeg提取大疆无人机MP4内嵌mov_text遥测SRT字幕并做完整性校验
python·ffmpeg·大疆·srt字幕·dji
幻影123!2 小时前
AlphaZero 五子棋实战(番外二):白棋专项训练的一种方案
python·机器学习·强化学习·五子棋·alpha zero
头发够用的程序员2 小时前
TensorRT 自定义算子插件实战(三):手搓 2×2 最大池化 customMaxpool
人工智能·pytorch·python·深度学习·神经网络·边缘计算·jetson
鬼手点金2 小时前
opencode-crawl4ai使用建议
java·开发语言·人工智能·python·机器学习
海天一色y2 小时前
深入理解对偶单纯形法:原理推导与 Python/MATLAB 编程实现
python·matlab·最优化算法·对偶单纯形法