python内存模型_内存篇3:CPython的内存管理架构-L2-块

本篇用到了C/C++的内存对齐的基础知识,我已经假定你有C/C++内存管理的相关基础。

我们在前一篇的流程图中留下了两个黑箱子,会涉及到内存模型第一层以上的其他话题,回顾下面关于第一层面向类型的内存API流程执行图。本篇要讨论其中一个黑箱就是何为物?

首先PyMem_这些函数族,在逻辑上是CPython内存模型架构的第1层,

再次,_PyObject_函数族一个衔接第1层和第2层的,衔接函数接口

pymalloc_alloc函数压根就不是分配器(不知道为何官方冠以默认分配器之名),更确切地说是一个调度函数,将来自外部CPython其他内部对象的内存空间请求是往第2层还是往第1层转发,显然当需要分配大于512字节时,调用前上图提到的PyMem_Raw前缀的函数族。

那么,我们不妨将前一篇内存模型架构图和上面的内存函数接口执行流程图结合一起,我们可以得到一个更为清晰的CPython内存模型架构图,图中提到aranas和pool是本篇需要提及的难点,

Layer 1与Layer 2的内存APIs的交互

不过在深入了解这个CPython的内存策略前,我们需要引入两个CPython的专业术语,CPython根据内存分配的尺寸的阀值512字节可以分为,对Python对象做如下分类:大于512字节的Python对象,称为大型对象(Big),而Arenas对象的尺寸为256KB就是CPython中大型对象因此Arenas对象的内存分配,CPython会选择调用PyMem_RawMalloc()或PyMem_RawRealloc()为其分配内存,换句话就是通过第0层去调用C库的malloc分配器,因此C底层的malloc分配器是仅供给arenas对象使用的。

少于或等于512字节的Python对象,称为小型对象(Small),小型对象的内存请求按该对象的类型尺寸分组,这些分组按8个字节对齐,由于返回的地址必须有效对齐。这些类型尺寸的对象的内存请求由4KB的内存池提供内存分配,当然前提是该内存池有闲置的块。

内存模型的第2层提到的PyObject_函数族,如下所示,它们位于Objects/obmalloc.c的第679行和第710行,具体的逻辑没必要好说,跟前篇提到内存函数接口是一致的。

void *

PyObject_Malloc(size_t size)

{

/* see PyMem_RawMalloc() */

if (size > (size_t)PY_SSIZE_T_MAX)

return NULL;

return _PyObject.malloc(_PyObject.ctx, size);

}

void *

PyObject_Calloc(size_t nelem, size_t elsize)

{

/* see PyMem_RawMalloc() */

if (elsize != 0 && nelem > (size_t)PY_SSIZE_T_MAX / elsize)

return NULL;

return _PyObject.calloc(_PyObject.ctx, nelem, elsize);

}

void *

PyObject_Realloc(void *ptr, size_t new_size)

{

/* see PyMem_RawMalloc() */

if (new_size > (size_t)PY_SSIZE_T_MAX)

return NULL;

return _PyObject.realloc(_PyObject.ctx, ptr, new_size);

}

void

PyObject_Free(void *ptr)

{

_PyObject.free(_PyObject.ctx, ptr);

}

void

PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)

{

*allocator = _PyObject_Arena;

}

void

PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)

{

_PyObject_Arena = *allocator;

}

我们这里的重点是要遗留的一个关键问题的默认的Python内存分配器,遗留的一些代码细节,我们先看看代码细节pymalloc_alloc位于源文件Objects/obmalloc.c的第1608行开始开始的代码细节。见下图红色标出的一些C代码。

上面的代码细节大意逻辑第一步:检索数组usepools中与申请的内存尺寸量相关的某个usepools元素,就是我们在上文插图(Layer 1与Layer 2的内存APIs的交互) 提到的pool,

第二步:在池中找到可用的内存块(bp=pool->freeblock),若找到旧返回该内存块,若找不到池中空闲的内存块就执行pymalloc_pool_extend函数。

第三步:若第一步中连可用的pool(第1612行)都找不到,就执行 allocate_from_new_pool函数

显然默认的Python内存分配器是直接驱动内存池,间接管理内存池的驱动函数。我们在代码中提取一些问题,它们就是本文后续随笔解答的一系列问题。,目前在本篇,我们稍微放下。第1609行的 usedpools是什么?poolp是什么数据类型?

第1610行的block是数据类型?

函数pymalloc_pool_extend(pool,size)的具体逻辑是什么?

allocate_from_new_pool(size)的具体逻辑是什么?

CPython的内存分配策略

CPython的内存管理策略,分3个不同级别的对象,分别是Arenas->pool->block,我先用一个思维导图,让你脑海中建立这三个对象的层次关系,读者可以先通过下图来初步理解这三个对象。这也是内存模型架构第2层中最为复杂堆内存托管逻辑。

Arenas->pool->block堆内存托管模型每个Arenas对象包装包含64个内存池,每个Arenas固定大小为256KB,并且该对象头部用两个struct area_object类型的指针在堆中构成Arenas对象的双重链表。

每个内存池(Pool),固有尺寸为4KB,每个内存池包含尺寸相同的逻辑块,并且并且该对象头部用两个struct pool_header类型的指针构成pool对象的双重链表。

块是封装Python对象的基本单位,对于Areas对象来说都按8字节的块来划分PyMem已分配的所有堆内存(备注:切入点1)。

块(Block)

CPython的内存管理策略中,首先定义逻辑上的“块”,并且用8字节对齐的方式确定块的尺寸,换句话说块的尺寸可以看作8的倍数那么大,例如你创建来一个25字节的Python对象,25字节不是8字节的倍数,那么CPython运行时系统会根据内存对齐的原则为该Python对象额外添加7个填充字节,就凑够32字节(8的倍数),更明确地说,对于一个实际尺寸位于25~32字节这个区间的任意Python对象,都能放入一个32字节的逻辑块中,

那么如此类推,我们在得到512字节以内,不同小型对象(Small)的内存请求在内存对齐后的内存块分配表。

小型对象的内存块分配表

事实上,我们所说的块,它的基本单位是8个字节,而对于CPython语义中,有着不同尺寸的block。对于少于512字节的任意Python对象的内存尺寸的分配,不同内存尺寸有对应的按8字节对齐后的块尺寸对应,w如上表所示的第2列中的8的倍数称为size class(类型尺寸),每种size class(类型尺寸)都由一个索引与其对应,我们称这些索引是size class index,由于所有块的尺寸是8字节对齐

CPython 3.6 之前 和 CPython 3.7之后 对内存块有了一些调整,对于CPython3.6之前的,我们说上表都是成立的,我们查看一下,具体链接https://github.com/python/cpython/blob/3.6/Objects/obmalloc.c

CPython 3.7的内存块对齐方式基于8个字节

目前网上很多同类型文章是基于CPython2.5或2.7版本为参考来理解CPython3.x的源代码,有个细节此类文章没有提到,那就是Objects/obmalloc.c有个细节没有详细提到的,那新版本的CPython3.7之后的小型对象的内存块分配表是就一定要8字节为基准吗?不一定!来看看关键的宏INDEX2SIZE(i),下面代码位于Objects/obmalloc.c的第846行到855行。

上面代码的宏SIZE_OF_P其实指代的是sizeof (void*) ,该宏定义在pyconfig.h的头文件中,CPython3.9默认指定SIZE_OF_P宏常量就为8,

也就是说对于CPython3.7之后的版本,小型对象的内存分配的基准是16字节对齐的,而不是8字节。这里我们尝试调用这个宏INDEX2SIZE(I),得到一些有趣的结果,可以查看如下测试代码(该测试代码中的宏定义是从CPython截取于源码文件Objects/obmalloc.c)

#include

#define uint unsigned int#define SIZEOF_VOID_P 8

#if SIZEOF_VOID_P > 4#define ALIGNMENT 16/* must be 2^N */#define ALIGNMENT_SHIFT 4#else#define ALIGNMENT 8/* must be 2^N */#define ALIGNMENT_SHIFT 3#endif

#define INDEX2SIZE(I) (((uint)(I) + 1) << ALIGNMENT_SHIFT)#define _Py_SIZE_ROUND_UP(n, a) (((size_t)(n) + \(size_t)((a) - 1)) & ~(size_t)((a) - 1))#define POOL_SIZE (4*1024)#define POOL_OVERHEAD _Py_SIZE_ROUND_UP(sizeof(struct pool_header), ALIGNMENT)

int main()

{

unsigned int size_class=0;

for(int i=0;i<=63;i++){

size_class=INDEX2SIZE(i);

if(size_class>512){

break;

}

printf("size-class: %d,size-class-idx:%d\n",size_class,i);

}

return 0;

}

我们看看运行结果,基于16字节的size class,的size class index是0,如此类推直到512字节

我们对上面的结果整理一下,会得到下面基于16字节对齐的小型对象的内存块分配表

基于16字节对齐的小型对象的内存块分配表

总结一个简单的公式size_class_idx=(size_class / ALIGNMENT)-1

小结:

本篇主要讨论了CPython内存模型架构第2层中,小型对象(小于512字节的对象)的内存分配原理的一个重要的概念block,以及什么是size class和size class index,那你是否思考过为什么在CPython 3.7之后,CPython的开发团队为何要将内存块的对齐基准从8字节调整到16字节呢?有兴趣的话,可以参考一下这个链接https://github.com/python/cpython/pull/12850,我这里就不细说啦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/428154.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件测试_入行

软件测试&#xff0c;入行太简单了。1. 看一本软件测试理论书籍 。2. 看一点数据库知识&#xff08;增删改查&#xff09;。3. 看一点Linux常用命令&#xff08;30个够了&#xff09;。4. 了解几个行业内容的测试工具&#xff08;如Jira Zentao LR Jmeter Fiddler 等&#xff0…

L1-056 猜数字 (20 分)

这道题做法倒是多样化&#xff0c;可以使用结构体&#xff0c;也可以使用多个数组做&#xff0c;不过为了复习一下好久没用过的结构体&#xff0c;我是用结构体做的。 这道题可以用来复习一下一些之前的简单的知识点&#xff0c;至于思路的话我相信应该都是有的。 这里就不发运…

uci数据集_数据分析找不到数据集?快来看这个盘点

前言数据集相对于机器学习而言是至关重要的&#xff0c;可以说好的数据集是成功的一半。但是&#xff0c;我们很难找到一个特定的数据集来解决各种机器学习问题&#xff0c;甚至是进行实验。因而找到合适的数据集是一件很难的事情&#xff0c;接下来我们就盘点一下一些优质的数…

git保留两个repo的commit并进行合并

以往的合并时首先要删除repo的.git文件夹&#xff0c;然后重新add-commit-push。带来的问题是会丢失某一个仓库的提交信息&#xff0c;不利于时光倒退。经过摸索终于实现了保留两个仓库提交信息的合并方法。介绍如下&#xff1a; 比如要将DownloadPicsBySeleniumAndPhantomJS这…

安利一款编辑神器——Markdown

经常使用word或者那些浏览器自带编辑器的你可能会和我有时候遇到一样的烦恼&#xff0c;调整格式可真是不容易&#xff0c;如果你也有同样的烦恼&#xff0c;不妨可以试一下Markdown&#xff0c;值得你拥有哈。 Markdown的使用 1.安装使用篇 a.首先安装软件&#xff0c;然后创…

python对图片颜色校正_使用Python PIL更改图像色调

Using Python PIL, Im trying to adjust the hue of a given image.Im not very comfortable with the jargon of graphics, so what I mean by “adjusting hue” is doing the Photoshop operation called “Hue/saturation”: this is to change the color of the image uni…

自动生成业务单据流水号方案

我们在开发管理软件的时候,常常遇到流水号(单据号、登记号)自动生成、控制和管理的问题。由于流水号具有唯一性和连续性的特点,在实际开发过程中若处理不好,会产生流水号重复及断号的问题。特别是多个并发用户同时保存一张同样的业务单据时&#xff0c;系统会返回多个相同的流水…

IDEA快捷键的使用成就手速之旅(要想手速变得快,快捷练习必须刚)

IDEA快捷键的使用 &#xff08;持续更新&#xff09; 1.必备合集 a.Ctrl CtrlF 文本代码查找神器呀简直CrtlR 文本代码替换CtrlZ 撤销&#xff08;基操基操&#xff09;CtrlY 删除当前行或者选中行 b.Shirt c.CtrlShirt d.Ctrlalt e.altShirt f.CtrlShirtalt 2.慢慢积…

一个项目部署多个节点会导致锁失效么_Redis分布式锁

分布式锁在很多场景中是非常有用的原语&#xff0c; 不同的进程必须以独占资源的方式实现资源共享就是一个典型的例子。有很多分布式锁的库和描述怎么实现分布式锁管理器(DLM)的博客,但是每个库的实现方式都不太一样&#xff0c;很多库的实现方式为了简单降低了可靠性&#xff…

GIT_服务器与本地环境构建

linux安装git包 很多yum源上自动安装的git版本为1.7&#xff0c;这里手动编译重新安装1&#xff1a;安装依赖包yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker2&#xff1a;删除已有的gityum remove git3&#xff1a…

Maven项目的基本创建步骤

先来个自己笔记的图片备忘一下&#xff0c;如果以后有时间再慢慢更新详细。

visualvm远程监控jvm_大型企业JVM实战:优化及面试热点分析

本次课程的笔记非常多&#xff0c;而且内容已经整理了好几个小时了&#xff0c;接着下来内容也会更多&#xff0c;也是大型企业JVM性能调优实战的最后一节&#xff0c;希望对你有帮助&#xff01;04&#xff1a;JVM性能监控与故障处理工具 大型企业JVM性能调优实战之总结17&…

Markdown图片路径的改变方法

Markdown图片路径的改变方法 Markdown用时一时爽&#xff0c;路径一改火葬场 Markdown在占用内存少的优点的同时&#xff0c;也注定了图片的存储不会是占用内存&#xff0c;而是根据路径和链接链到md文件里的。 相信有不少人会像我一样在做完一个Markdown笔记后&#xff0c;在…

SROP

title: SROP date: 2018-02-21 19:58:12 categories: 栈溢出 tags: - CTF - PWN - 栈溢出 SROP全称为 Sigreturn Oriented Programming &#xff0c;表明利用sigreturn这个函数实现ROP的技术。 参考资料 http://www.freebuf.com/articles/network/87447.htmlhttp://bobao.360.c…

python字符串的方法和列表的方法_Python学习笔记字符串操作之join()和split()方法,列表转字符串,字符串转列表...

随笔记录方便自己和同路人查阅。#------------------------------------------------我是可耻的分割线-------------------------------------------如果有一个字符串列表&#xff0c;需要将它们连接起来&#xff0c;成为一个单独的字符串&#xff0c;join()方法就很有用。join…

变量的比较之equals 与 == 的区别

Java的数据类型分为两种 1.基本数据类型&#xff0c;byte,short,char,int,long,float,double,boolean&#xff0c;只要使用运算符就可以了&#xff0c;进行比较只是简单进行比较其中的字节组合。 两个引用变量是否引用到堆上的同一个对象&#xff0c;也可以使用。 2.复杂的对象…

scheduledthreadpoolexecutor使用_ScheduledThreadPoolExecutor详解

本文主要分为两个部分&#xff0c;第一部分首先会对ScheduledThreadPoolExecutor进行简单的介绍&#xff0c;并且会介绍其主要API的使用方式&#xff0c;然后介绍了其使用时的注意点&#xff0c;第二部分则主要对ScheduledThreadPoolExecutor的实现细节进行介绍。1. 使用简介Sc…

SpringContextHolder 静态持有SpringContext的引用

SpringContextHolder 静态持有SpringContext的引用 package com.test.quartz;import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware;/**** 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何…

canvas绘制图像image

canvas绘制图像image 1.image的三个script的基本语法 准备工作:1.定义画布长度&#xff0c;获取2D绘图环境 ​ 2.建立对面对象&#xff0c;设置图片路径 ​ 3.载入图片&#xff0c;开始绘制 a.简单的画布上根据坐标绘制 ctx.drawImage(img,x,y) img为要绘制的图像&#…

根据时间戳生成编号_分布式系统的唯一ID生成算法对比

在复杂分布式系统中&#xff0c;往往需要对大量的数据和消息进行唯一标识。那么如何实现全局唯一id呢&#xff1f;有以下几种方案。(1)方案一&#xff1a;独立数据库自增id这个方案就是说你的系统每次要生成一个id&#xff0c;都是往一个独立库的一个独立表里插入一条没什么业务…