Python源码阅读-内存管理机制(一)

来源:互联网 发布:mac文件拷贝不到u盘 编辑:程序博客网 时间:2024/05/19 12:15

Python的内存管理架构

基本分层

Objects/obmalloc.c源码中, 给了一个分层划分

    _____   ______   ______       ________   [ int ] [ dict ] [ list ] ... [ string ]       Python core         |+3 | <----- Object-specific memory -----> | <-- Non-object memory --> |    _______________________________       |                           |   [   Python's object allocator   ]      |                           |+2 | ####### Object memory ####### | <------ Internal buffers ------> |    ______________________________________________________________    |   [          Python's raw memory allocator (PyMem_ API)          ]   |+1 | <----- Python memory (under PyMem manager's control) ------> |   |    __________________________________________________________________   [    Underlying general-purpose allocator (ex: C library malloc)   ] 0 | <------ Virtual memory allocated for the python process -------> |   =========================================================================    _______________________________________________________________________   [                OS-specific Virtual Memory Manager (VMM)               ]-1 | <--- Kernel dynamic storage allocation & management (page-based) ---> |    __________________________________   __________________________________   [                                  ] [                                  ]-2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> |

可以看到

layer 3: Object-specific memory(int/dict/list/string....)         Python 实现并维护         更高抽象层次的内存管理策略, 主要是各类特定对象的缓冲池机制. 具体见前面几篇涉及的内存分配机制 layer 2: Python's object allocator         Python 实现并维护         实现了创建/销毁Python对象的接口(PyObject_New/Del), 涉及对象参数/引用计数等 layer 1: Python's raw memory allocator (PyMem_ API)         Python 实现并维护, 包装了第0层的内存管理接口, 提供统一的raw memory管理接口         封装的原因: 不同操作系统 C 行为不一定一致, 保证可移植性, 相同语义相同行为 layer 0: Underlying general-purpose allocator (ex: C library malloc)         操作系统提供的内存管理接口, 由操作系统实现并管理, Python不能干涉这一层的行为

第三层layer 3前面已经介绍过了, 几乎每种常用的数据类型都伴有一套缓冲池机制.

在这里, 我们关注的是layer 2/1

简要介绍下layer 1, 然后重点关注layer 2, 这才是重点!!!主要就这两层是python的主要操作。

layer 1: PyMem_ API

PyMem_ API是对操作系统内存管理接口进行的封装

查看pymem.h可以看到

// Raw memory interface// 这里存在三个宏定义, 宏可以避免一次函数调用的开销, 提高运行效率// 不允许非配空间大小为0的内存空间#define PyMem_MALLOC(n)     ((size_t)(n) > (size_t)PY_SSIZE_T_MAX ? NULL                 : malloc((n) ? (n) : 1)) #define PyMem_REALLOC(p, n) ((size_t)(n) > (size_t)PY_SSIZE_T_MAX  ? NULL                 : realloc((p), (n) ? (n) : 1))#define PyMem_FREE      free // 这里做了三个函数的声明, 平台独立的 malloc/realloc/freePyAPI_FUNC(void *) PyMem_Malloc(size_t);PyAPI_FUNC(void *) PyMem_Realloc(void *, size_t);PyAPI_FUNC(void) PyMem_Free(void *); // ============================================================ // Type-oriented memory interface// 这里还有三个类型相关的内存接口, 批量分配/重分配 n 个 类型为 type内存#define PyMem_New(type, n)   ( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :      ( (type *) PyMem_Malloc((n) * sizeof(type)) ) )#define PyMem_NEW(type, n)   ( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :      ( (type *) PyMem_MALLOC((n) * sizeof(type)) ) ) #define PyMem_Resize(p, type, n)   ( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :        (type *) PyMem_Realloc((p), (n) * sizeof(type)) )#define PyMem_RESIZE(p, type, n)   ( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL :        (type *) PyMem_REALLOC((p), (n) * sizeof(type)) )
然后object.c中, 我们关注实现, 三个实现的函数调用了对应的宏

// 使用 C 写Python扩展模块时使用函数而不是对应的宏void *PyMem_Malloc(size_t nbytes){    return PyMem_MALLOC(nbytes);} void *PyMem_Realloc(void *p, size_t nbytes){    return PyMem_REALLOC(p, nbytes);} voidPyMem_Free(void *p){    PyMem_FREE(p);}

这些接口都相对简单

好了, 结束, 开始关注layer 2: Python's object allocator

Python 的内存分配策略

先来看Objects/obmalloc.c中的一段注释

/* * "Memory management is where the rubber meets the road -- if we do the wrong * thing at any level, the results will not be good. And if we don't make the * levels work well together, we are in serious trouble." (1) * * (1) Paul R. Wilson, Mark S. Johnstone, Michael Neely, and David Boles, *    "Dynamic Storage Allocation: A Survey and Critical Review", *    in Proc. 1995 Int'l. Workshop on Memory Management, September 1995. */

Python引入了内存池机制, 用于管理对小块内存的申请和释放

逻辑

1. 如果要分配的内存空间大于 SMALL_REQUEST_THRESHOLD bytes(512 bytes), 将直接使用layer 1的内存分配接口进行分配2. 否则, 使用不同的block来满足分配需求

整个小块内存池可以视为一个层次结构

block

Python内存的最小单位, 所有block长度都是8字节对齐的

注意这里block只是一个概念, 在源代码中并没有实体存在.

不同类型block, 对应不同内存大小, 这个内存大小的值被称为size class.

不同长度的block

例如

图示:

注意: 这里有个Size class idx, 这个主要为了后面pool中用到

size classsize class index之间的转换

pool

pool管理block, 一个pool管理着一堆有固定大小的内存块

本质: pool管理着一大块内存, 它有一定的策略, 将这块大的内存划分为多个大小一致的小块内存.

pool size

在Python中, 一个pool的大小通常为一个系统内存页. 4kB

pool组成

pool的4kB内存 = pool_header + block集合(N多大小一样的block)

pool_header

pool_header的作用

结构图:

pool初始化

从内存中初始化一个全新的空的pool

Objects/obmalloc.c

初始化后的图

pool进行block分配 – 0 总体代码

总体分配的代码如下

pool进行block分配 – 1 刚开始

内存块尚未分配完, 且此时不存在回收的block, 全新进来的时候, 分配第一块block

所以进入的逻辑是代码-2

结果图示

pool进行block分配 – 2 回收了某几个block

回收涉及的代码

没释放一个block, 该block就会变成 pool->freeblock 的头节点, 而单链表一个节点如何指向下一个节点呢? 通过赋值, 节点内存空间保存着下个节点的地址, 最后一个节点指向NULL(知道上面代码-1的判断条件了吧>_

假设已经连续分配了5块, 第1块和第4块被释放

此时内存图示

此时再一个block分配调用进来, 执行分配, 进入的逻辑是代码-1

pool进行block分配 – 3 pool用完了

pool中内存空间都用完了, 进入代码-3

获取下一个pool(链表上每个pool的block size都是一致的)

初始化后的图

pool进行block分配 – 0 总体代码

总体分配的代码如下

pool进行block分配 – 1 刚开始

内存块尚未分配完, 且此时不存在回收的block, 全新进来的时候, 分配第一块block

所以进入的逻辑是代码-2

结果图示

pool进行block分配 – 2 回收了某几个block

回收涉及的代码

没释放一个block, 该block就会变成 pool->freeblock 的头节点, 而单链表一个节点如何指向下一个节点呢? 通过赋值, 节点内存空间保存着下个节点的地址, 最后一个节点指向NULL(知道上面代码-1的判断条件了吧>_

假设已经连续分配了5块, 第1块和第4块被释放

此时内存图示

此时再一个block分配调用进来, 执行分配, 进入的逻辑是代码-1

pool进行block分配 – 3 pool用完了

pool中内存空间都用完了, 进入代码-3

获取下一个pool(链表上每个pool的block size都是一致的)






初始化后的图

pool进行block分配 – 0 总体代码

总体分配的代码如下

pool进行block分配 – 1 刚开始

内存块尚未分配完, 且此时不存在回收的block, 全新进来的时候, 分配第一块block

所以进入的逻辑是代码-2

结果图示

pool进行block分配 – 2 回收了某几个block

回收涉及的代码

没释放一个block, 该block就会变成 pool->freeblock 的头节点, 而单链表一个节点如何指向下一个节点呢? 通过赋值, 节点内存空间保存着下个节点的地址, 最后一个节点指向NULL(知道上面代码-1的判断条件了吧>_

假设已经连续分配了5块, 第1块和第4块被释放

此时内存图示

此时再一个block分配调用进来, 执行分配, 进入的逻辑是代码-1

pool进行block分配 – 3 pool用完了

pool中内存空间都用完了, 进入代码-3

获取下一个pool(链表上每个pool的block size都是一致的)

原创粉丝点击