内存管理

概述

python中的内存管理涉及一个包含所有python对象和数据结构的私有堆。此私有堆的管理由 python内存管理器 . python内存管理器有不同的组件,它们处理各种动态存储管理方面,如共享、分段、预分配或缓存。

在最低级别,原始内存分配器通过与操作系统的内存管理器交互,确保私有堆中有足够的空间存储所有与Python相关的数据。在原始内存分配器之上,多个特定于对象的分配器在同一堆上操作,并实现不同的内存管理策略,以适应每种对象类型的特性。例如,整数对象在堆中的管理方式与字符串、元组或字典不同,因为整数意味着不同的存储要求和速度/空间权衡。因此,python内存管理器将一些工作委托给特定于对象的分配器,但确保后者在私有堆的边界内操作。

理解python堆的管理是由解释器本身执行的,并且用户无法控制它,这一点很重要,即使他们经常操纵指向堆内内存块的对象指针。python内存管理器根据需要通过本文中列出的python/c api函数为python对象和其他内部缓冲区分配堆空间。

为了避免内存损坏,扩展编写器不应尝试使用C库导出的函数对python对象进行操作: malloc()calloc()realloc()free() . 这将导致C分配器和Python内存管理器之间的混合调用产生致命的后果,因为它们实现不同的算法并在不同的堆上操作。但是,可以使用C库分配器安全地为各个目的分配和释放内存块,如下例所示:

PyObject *res;
char *buf = (char *) malloc(BUFSIZ); /* for I/O */

if (buf == NULL)
    return PyErr_NoMemory();
...Do some I/O operation involving buf...
res = PyBytes_FromString(buf);
free(buf); /* malloc'ed */
return res;

在本例中,I/O缓冲区的内存请求由C库分配器处理。python内存管理器只参与分配结果返回的bytes对象。

但是,在大多数情况下,建议从python堆分配内存,因为后者受python内存管理器控制。例如,当用C语言编写的新对象类型扩展解释器时,这是必需的。使用Python堆的另一个原因是希望 通知 关于扩展模块内存需求的python内存管理器。即使请求的内存专门用于内部的、高度特定的目的,将所有内存请求委托给python内存管理器也会使解释器从整体上获得更精确的内存占用图像。因此,在某些情况下,python内存管理器可能会或可能不会触发适当的操作,如垃圾收集、内存压缩或其他预防过程。注意,通过使用C库分配器(如前一个示例所示),为I/O缓冲区分配的内存将完全溢出Python内存管理器。

参见

这个 PYTHONMALLOC 环境变量可用于配置Python使用的内存分配器。

这个 PYTHONMALLOCSTATS 环境变量可用于打印 pymalloc memory allocator 每次创建新的PymalLoc对象竞技场时,以及关闭时。

分配器域

所有分配函数都属于以下三个不同的“域”之一(另请参阅 :c:type`PyMemAllocatorDomain`). These domains represent different allocation strategies and are optimized for different purposes. The specific details on how every domain allocates memory or what internal functions each domain calls is considered an implementation detail, but for debugging purposes a simplified table can be found at here 。没有硬性要求将属于给定域的分配函数返回的内存仅用于该域提示的目的(尽管这是推荐的做法)。例如,可以使用由返回的内存 PyMem_RawMalloc() 用于分配Python对象或由 PyObject_Malloc() 用于为缓冲区分配内存。

这三个分配域是:

  • 原始域:用于为通用内存缓冲区分配内存,其中分配 must 转到系统分配器,或者转到分配器可以在没有 GIL 。内存是直接向系统请求的。

  • “Mem”域:用于为Python缓冲区和通用内存缓冲区分配内存,其中分配必须使用 GIL 等一下。内存取自Python私有堆。

  • 对象域:用于分配属于Python对象的内存。内存取自Python私有堆。

当释放先前由属于给定域的分配函数分配的内存时,必须使用匹配的特定释放函数。例如, PyMem_Free() 必须用于释放使用 PyMem_Malloc()

原始内存接口

以下函数集是系统分配器的封装器。这些函数是线程安全的, GIL 不需要持有。

这个 default raw memory allocator 使用以下功能: malloc()calloc()realloc()free() 调用 malloc(1) (或) calloc(1, 1) )请求零字节时。

3.4 新版功能.

void *PyMem_RawMalloc(size_t n)

分配 n 字节并返回类型的指针 void* 分配的内存,或 NULL 如果请求失败。

如果可能的话,请求零字节将返回一个不同的非“NULL”指针,就像 PyMem_RawMalloc(1) 而是被叫过来的。内存将不会以任何方式初始化。

void *PyMem_RawCalloc(size_t nelem, size_t elsize)

分配 尼勒姆 每个元素的字节大小为 放大 并返回类型的指针 void* 分配的内存,或 NULL 如果请求失败。内存初始化为零。

如果可能,请求零元素或零字节大小的元素将返回一个不同的非“NULL”指针,就像 PyMem_RawCalloc(1, 1) 而是被叫过来的。

3.5 新版功能.

void *PyMem_RawRealloc(void *p, size_t n)

调整指向的内存块的大小 pn 字节。内容物将保持旧尺寸和新尺寸的最小值。

如果 pNULL ,调用等价于 PyMem_RawMalloc(n) 否则 n 等于零,内存块将调整大小但不释放,返回的指针为非-``NULL`。

除非 pNULL ,它必须由以前的调用返回 PyMem_RawMalloc()PyMem_RawRealloc()PyMem_RawCalloc() .

如果请求失败, PyMem_RawRealloc() 收益率 NULLp 仍然是指向上一个内存区的有效指针。

void PyMem_RawFree(void *p)

释放指向的内存块 p ,它必须由以前的调用返回 PyMem_RawMalloc()PyMem_RawRealloc()PyMem_RawCalloc() . 否则,或如果 PyMem_RawFree(p) 以前调用过,发生未定义的行为。

如果 pNULL ,不执行任何操作。

内存接口

以下函数集是按照ANSIC标准建模的,但在请求零字节时指定行为,可用于从python堆分配和释放内存。

这个 default memory allocator 使用 pymalloc memory allocator .

警告

这个 GIL 使用这些功能时必须保持。

在 3.6 版更改: 默认的分配器现在是pymalloc而不是system malloc() .

void *PyMem_Malloc(size_t n)

分配 n 字节并返回类型的指针 void* 分配的内存,或 NULL 如果请求失败。

如果可能的话,请求零字节将返回一个不同的非“NULL”指针,就像 PyMem_Malloc(1) 而是被叫过来的。内存将不会以任何方式初始化。

void *PyMem_Calloc(size_t nelem, size_t elsize)

分配 尼勒姆 每个元素的字节大小为 放大 并返回类型的指针 void* 分配的内存,或 NULL 如果请求失败。内存初始化为零。

如果可能,请求零元素或零字节大小的元素将返回一个不同的非“NULL”指针,就像 PyMem_Calloc(1, 1) 而是被叫过来的。

3.5 新版功能.

void *PyMem_Realloc(void *p, size_t n)

调整指向的内存块的大小 pn 字节。内容物将保持旧尺寸和新尺寸的最小值。

如果 pNULL ,调用等价于 PyMem_Malloc(n) 否则 n 等于零,内存块将调整大小但不释放,返回的指针为非-``NULL`。

除非 pNULL ,它必须由以前的调用返回 PyMem_Malloc()PyMem_Realloc()PyMem_Calloc() .

如果请求失败, PyMem_Realloc() 收益率 NULLp 仍然是指向上一个内存区的有效指针。

void PyMem_Free(void *p)

释放指向的内存块 p ,它必须由以前的调用返回 PyMem_Malloc()PyMem_Realloc()PyMem_Calloc() . 否则,或如果 PyMem_Free(p) 以前调用过,发生未定义的行为。

如果 pNULL ,不执行任何操作。

为方便起见,提供了以下面向类型的宏。注意 TYPE 指任何C型。

TYPE *PyMem_New(TYPE, size_t n)

等同于 PyMem_Malloc() 但分配 (n * sizeof(TYPE)) 内存字节。返回指针强制转换为 TYPE* . 内存将不会以任何方式初始化。

TYPE *PyMem_Resize(void *p, TYPE, size_t n)

等同于 PyMem_Realloc() ,但内存块的大小调整为 (n * sizeof(TYPE)) 字节。返回指针强制转换为 TYPE* . 返回时, p 将是指向新内存区的指针,或 NULL 如果发生故障。

这是一个C预处理器宏; p 总是重新分配。保存的原始值 p 以避免在处理错误时丢失内存。

void PyMem_Del(void *p)

等同于 PyMem_Free() .

此外,还提供了以下宏集,用于直接调用python内存分配器,而不涉及上面列出的C API函数。但是,请注意,它们的使用并不能保持跨Python版本的二进制兼容性,因此在扩展模块中不推荐使用。

  • PyMem_MALLOC(size)

  • PyMem_NEW(type, size)

  • PyMem_REALLOC(ptr, size)

  • PyMem_RESIZE(ptr, type, size)

  • PyMem_FREE(ptr)

  • PyMem_DEL(ptr)

对象分配器

以下函数集是按照ANSIC标准建模的,但在请求零字节时指定行为,可用于从python堆分配和释放内存。

注解

中描述的方法截取此域中的分配函数时,不能保证这些分配器返回的内存可以成功强制转换为Python对象 Customize Memory Allocators 部分。

这个 default object allocator 使用 pymalloc memory allocator .

警告

这个 GIL 使用这些功能时必须保持。

void *PyObject_Malloc(size_t n)

分配 n 字节并返回类型的指针 void* 分配的内存,或 NULL 如果请求失败。

如果可能的话,请求零字节将返回一个不同的非“NULL”指针,就像 PyObject_Malloc(1) 而是被叫过来的。内存将不会以任何方式初始化。

void *PyObject_Calloc(size_t nelem, size_t elsize)

分配 尼勒姆 每个元素的字节大小为 放大 并返回类型的指针 void* 分配的内存,或 NULL 如果请求失败。内存初始化为零。

如果可能,请求零元素或零字节大小的元素将返回一个不同的非“NULL”指针,就像 PyObject_Calloc(1, 1) 而是被叫过来的。

3.5 新版功能.

void *PyObject_Realloc(void *p, size_t n)

调整指向的内存块的大小 pn 字节。内容物将保持旧尺寸和新尺寸的最小值。

如果 pNULL ,调用等价于 PyObject_Malloc(n) 否则 n 等于零,内存块将调整大小但不释放,返回的指针为非-``NULL`。

除非 pNULL ,它必须由以前的调用返回 PyObject_Malloc()PyObject_Realloc()PyObject_Calloc() .

如果请求失败, PyObject_Realloc() 收益率 NULLp 仍然是指向上一个内存区的有效指针。

void PyObject_Free(void *p)

释放指向的内存块 p ,它必须由以前的调用返回 PyObject_Malloc()PyObject_Realloc()PyObject_Calloc() . 否则,或如果 PyObject_Free(p) 以前调用过,发生未定义的行为。

如果 pNULL ,不执行任何操作。

默认内存分配器

默认内存分配器:

配置

名字

PyMem_RawMalloc

PyMem_Malloc

PyObject_Malloc

释放构建

"pymalloc"

malloc

pymalloc

pymalloc

调试生成

"pymalloc_debug"

malloc +调试

pymalloc +调试

pymalloc +调试

发布版本,不带pymalloc

"malloc"

malloc

malloc

malloc

调试生成,不带pymalloc

"malloc_debug"

malloc +调试

malloc +调试

malloc +调试

传说:

自定义内存分配器

3.4 新版功能.

type PyMemAllocatorEx

用于描述内存块分配器的结构。结构有四个字段:

意义

void *ctx

作为第一个参数传递的用户上下文

void* malloc(void *ctx, size_t size)

分配内存块

void* calloc(void *ctx, size_t nelem, size_t elsize)

分配一个初始化为零的内存块

void* realloc(void *ctx, void *ptr, size_t new_size)

分配或调整内存块的大小

void free(void *ctx, void *ptr)

释放内存块

在 3.5 版更改: 这个 PyMemAllocator 结构已重命名为 PyMemAllocatorEx 和一个新的 calloc 字段已添加。

type PyMemAllocatorDomain

用于标识分配器域的枚举。领域:

PYMEM_DOMAIN_RAW

功能:

PYMEM_DOMAIN_MEM

功能:

PYMEM_DOMAIN_OBJ

功能:

void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

获取指定域的内存块分配器。

void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)

设置指定域的内存块分配器。

当请求零字节时,新分配器必须返回一个不同的非“NULL”指针。

对于 PYMEM_DOMAIN_RAW 域,分配器必须是线程安全的: GIL 在调用分配器时不保留。

如果新的分配器不是一个钩子(不调用前一个分配器),则 PyMem_SetupDebugHooks() 必须调用函数才能在新分配器的顶部重新安装调试挂钩。

void PyMem_SetupDebugHooks(void)

安装程序挂接以检测python内存分配器函数中的错误。

新分配的内存中充满了字节 0xCD (CLEANBYTE 0xDD (DEADBYTE (FORBIDDENBYTE 0xFD

运行时检查:

  • 检测API违规,例如: PyObject_Free() 调用由分配的缓冲区 PyMem_Malloc()

  • 在缓冲区启动前检测写入(缓冲区下溢)

  • 在缓冲区结束后检测写入(缓冲区溢出)

  • 检查一下 GIL 当分配器的函数 PYMEM_DOMAIN_OBJ (EX: PyObject_Malloc()PYMEM_DOMAIN_MEM (EX: PyMem_Malloc() )域被调用

出错时,调试挂钩使用 tracemalloc 模块以获取分配内存块的跟踪。只有当 tracemalloc 正在跟踪python内存分配,内存块已被跟踪。

这些钩子是 installed by default 如果在调试模式下编译python。这个 PYTHONMALLOC 环境变量可用于在以发布模式编译的Python上安装调试挂钩。

在 3.6 版更改: 这个函数现在也可以在以发布模式编译的Python上工作。出错时,调试挂钩现在使用 tracemalloc 以获取分配内存块的跟踪。调试钩子现在还检查当 PYMEM_DOMAIN_OBJPYMEM_DOMAIN_MEM 调用域。

在 3.8 版更改: 0xCB (CLEANBYTE0xDB (DEADBYTE0xFB (FORBIDDENBYTE 0xCD0xDD0xFD malloc()free() .

pymalloc分配器

Python有一个 聚酰亚胺 为生存期短的小对象(小于或等于512字节)优化的分配器。它使用称为“arenas”的内存映射,固定大小为256 kib。它又回到 PyMem_RawMalloc()PyMem_RawRealloc() 对于大于512字节的分配。

聚酰亚胺default allocatorPYMEM_DOMAIN_MEM (EX: PyMem_Malloc()PYMEM_DOMAIN_OBJ (EX: PyObject_Malloc() )域。

竞技场分配器使用以下功能:

  • VirtualAlloc()VirtualFree() 在Windows上,

  • mmap()munmap() 如果可用,

  • malloc()free() 否则。

自定义pymalloc竞技场分配器

3.4 新版功能.

type PyObjectArenaAllocator

用于描述竞技场分配器的结构。结构有三个字段:

意义

void *ctx

作为第一个参数传递的用户上下文

void* alloc(void *ctx, size_t size)

分配一个字节大小的竞技场

void free(void *ctx, size_t size, void *ptr)

解放竞技场

void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)

获取竞技场分配程序。

void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)

设置竞技场分配器。

tracemalloc C应用程序接口

3.7 新版功能.

int PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr, size_t size)

跟踪 tracemalloc 模块。

返回 0 成功,回报 -1 错误时(无法分配内存来存储跟踪)。返回 -2 如果tracemaloc被禁用。

如果已跟踪内存块,请更新现有跟踪。

int PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)

tracemalloc 模块。如果这个街区没有被追踪,什么也不要做。

返回 -2 如果tracemaloc被禁用,则返回 0 .

实例

以下是第节中的示例 概述 ,重写以便使用第一个函数集从python堆分配I/O缓冲区::

PyObject *res;
char *buf = (char *) PyMem_Malloc(BUFSIZ); /* for I/O */

if (buf == NULL)
    return PyErr_NoMemory();
/* ...Do some I/O operation involving buf... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* allocated with PyMem_Malloc */
return res;

使用面向类型的函数集的相同代码:

PyObject *res;
char *buf = PyMem_New(char, BUFSIZ); /* for I/O */

if (buf == NULL)
    return PyErr_NoMemory();
/* ...Do some I/O operation involving buf... */
res = PyBytes_FromString(buf);
PyMem_Del(buf); /* allocated with PyMem_New */
return res;

注意,在上面的两个例子中,缓冲区总是通过属于同一集合的函数来操作。事实上,对于给定的内存块,需要使用相同的内存API系列,以便将混合不同分配器的风险降到最低。以下代码序列包含两个错误,其中一个被标记为 致命的 因为它混合了在不同堆上运行的两个不同的分配器。::

char *buf1 = PyMem_New(char, BUFSIZ);
char *buf2 = (char *) malloc(BUFSIZ);
char *buf3 = (char *) PyMem_Malloc(BUFSIZ);
...
PyMem_Del(buf3);  /* Wrong -- should be PyMem_Free() */
free(buf2);       /* Right -- allocated via malloc() */
free(buf1);       /* Fatal -- should be PyMem_Del()  */

除了旨在处理来自python堆的原始内存块的函数外,python中的对象还通过 PyObject_New()PyObject_NewVar()PyObject_Del() .

这些将在下一章中解释,在C中定义和实现新的对象类型。