如何使用网站模板,网站建设是不是可以免费建站,口碑最好的家装公司,做摘抄的网站本篇用到了C/C的内存对齐的基础知识#xff0c;我已经假定你有C/C内存管理的相关基础。我们在前一篇的流程图中留下了两个黑箱子,会涉及到内存模型第一层以上的其他话题#xff0c;回顾下面关于第一层面向类型的内存API流程执行图。本篇要讨论其中一个黑箱就是何为物#xf…本篇用到了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);}voidPyObject_Free(void *ptr){_PyObject.free(_PyObject.ctx, ptr);}voidPyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator){*allocator _PyObject_Arena;}voidPyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator){_PyObject_Arena *allocator;}我们这里的重点是要遗留的一个关键问题的默认的Python内存分配器遗留的一些代码细节我们先看看代码细节pymalloc_alloc位于源文件Objects/obmalloc.c的第1608行开始开始的代码细节。见下图红色标出的一些C代码。上面的代码细节大意逻辑第一步检索数组usepools中与申请的内存尺寸量相关的某个usepools元素就是我们在上文插图(Layer 1与Layer 2的内存APIs的交互) 提到的pool第二步:在池中找到可用的内存块(bppool-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的倍数)更明确地说对于一个实际尺寸位于2532字节这个区间的任意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.cCPython 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_class0;for(int i0;i63;i){size_classINDEX2SIZE(i);if(size_class512){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我这里就不细说啦。