内存管理 — Python 文档
内存管理
概述
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 内存管理器仅参与作为结果返回的字节对象的分配。
但是,在大多数情况下,建议专门从 Python 堆中分配内存,因为后者受 Python 内存管理器的控制。 例如,当用 C 编写的新对象类型扩展解释器时,这是必需的。 使用 Python 堆的另一个原因是希望 通知 Python 内存管理器关于扩展模块的内存需求。 即使请求的内存专门用于内部、高度特定的目的,将所有内存请求委托给 Python 内存管理器也会使解释器对整个内存占用有更准确的图像。 因此,在某些情况下,Python 内存管理器可能会也可能不会触发适当的操作,例如垃圾收集、内存压缩或其他预防程序。 请注意,通过使用前面示例中所示的 C 库分配器,为 I/O 缓冲区分配的内存完全转义了 Python 内存管理器。
也可以看看
PYTHONMALLOC 环境变量可用于配置 Python 使用的内存分配器。
PYTHONMALLOCSTATS 环境变量可用于在每次创建新的 pymalloc 对象 arena 和关闭时打印 pymalloc 内存分配器 的统计信息。
原始内存接口
以下函数集是系统分配器的包装器。 这些函数是线程安全的,不需要持有 GIL。
默认原始内存分配器使用以下函数: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)
分配 nelem 元素,每个元素的字节大小为 elsize 并返回 void* 类型的指针到分配的内存,或
NULL
如果请求失败。 内存初始化为零。如果可能,请求零元素或大小为零字节的元素会返回一个不同的非
NULL
指针,就好像PyMem_RawCalloc(1, 1)
已被调用一样。3.5 版中的新功能。
- void *PyMem_RawRealloc(void *p, size_t n)
将 p 指向的内存块大小调整为 n 字节。 内容将保持不变,以旧尺寸和新尺寸中的最小值为准。
如果p为
NULL
,则调用等价于PyMem_RawMalloc(n)
; 否则如果 n 等于 0,则内存块被调整大小但不会被释放,并且返回的指针是非NULL
。除非 p 是
NULL
,否则它必须由先前对 PyMem_RawMalloc()、PyMem_RawRealloc() 或 PyMem_RawMalloc() 的调用返回()。如果请求失败,PyMem_RawRealloc() 返回
NULL
并且 p 仍然是指向前一个内存区域的有效指针。
- void PyMem_RawFree(void *p)
释放 p 指向的内存块,该内存块必须由先前对 PyMem_RawMalloc()、PyMem_RawRealloc() 或 PyCalloc() 的调用返回)。 否则,或者如果
PyMem_RawFree(p)
之前已被调用,则会发生未定义的行为。如果p为
NULL
,则不执行任何操作。
内存接口
以下函数集以 ANSI C 标准为模型,但在请求零字节时指定行为,可用于从 Python 堆分配和释放内存。
3.6 版更改: 默认分配器现在是 pymalloc 而不是系统 malloc()
。
- void *PyMem_Malloc(size_t n)
分配 n 字节并返回类型为 void* 的指针到分配的内存,如果请求失败则返回
NULL
。如果可能,请求零字节将返回一个不同的非
NULL
指针,就好像PyMem_Malloc(1)
已被调用一样。 内存不会以任何方式初始化。
- void *PyMem_Calloc(size_t nelem, size_t elsize)
分配 nelem 元素,每个元素的字节大小为 elsize 并返回 void* 类型的指针到分配的内存,或
NULL
如果请求失败。 内存初始化为零。如果可能,请求零元素或大小为零字节的元素会返回一个不同的非
NULL
指针,就好像PyMem_Calloc(1, 1)
已被调用一样。3.5 版中的新功能。
- void *PyMem_Realloc(void *p, size_t n)
将 p 指向的内存块大小调整为 n 字节。 内容将保持不变,以旧尺寸和新尺寸中的最小值为准。
如果p为
NULL
,则调用等价于PyMem_Malloc(n)
; 否则如果 n 等于 0,则内存块被调整大小但不会被释放,并且返回的指针是非NULL
。除非 p 是
NULL
,否则它必须由先前对 PyMem_Malloc()、PyMem_Realloc() 或 PyMem_Call 的调用返回()。如果请求失败,PyMem_Realloc() 返回
NULL
并且 p 仍然是指向前一个内存区域的有效指针。
- void PyMem_Free(void *p)
释放 p 指向的内存块,该内存块必须由先前调用 PyMem_Malloc()、PyMem_Realloc() 或 PyMem_Calloc( )。 否则,或者如果
PyMem_Free(p)
之前已被调用,则会发生未定义的行为。如果p为
NULL
,则不执行任何操作。
为方便起见,提供了以下面向类型的宏。 请注意,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)
对象分配器
以下函数集以 ANSI C 标准为模型,但在请求零字节时指定行为,可用于从 Python 堆分配和释放内存。
- void *PyObject_Malloc(size_t n)
分配 n 字节并返回类型为 void* 的指针到分配的内存,如果请求失败则返回
NULL
。如果可能,请求零字节将返回一个不同的非
NULL
指针,就好像PyObject_Malloc(1)
已被调用一样。 内存不会以任何方式初始化。
- void *PyObject_Calloc(size_t nelem, size_t elsize)
分配 nelem 元素,每个元素的字节大小为 elsize 并返回 void* 类型的指针到分配的内存,或
NULL
如果请求失败。 内存初始化为零。如果可能,请求零元素或大小为零字节的元素会返回一个不同的非
NULL
指针,就好像PyObject_Calloc(1, 1)
已被调用一样。3.5 版中的新功能。
- void *PyObject_Realloc(void *p, size_t n)
将 p 指向的内存块大小调整为 n 字节。 内容将保持不变,以旧尺寸和新尺寸中的最小值为准。
如果p为
NULL
,则调用等价于PyObject_Malloc(n)
; 否则如果 n 等于 0,则内存块被调整大小但不会被释放,并且返回的指针是非NULL
。除非 p 是
NULL
,否则它必须由先前对 PyObject_Malloc()、PyObject_Realloc() 或 PyObject_Calloc 的调用返回()。如果请求失败,PyObject_Realloc() 返回
NULL
并且 p 仍然是指向前一个内存区域的有效指针。
- void PyObject_Free(void *p)
释放 p 指向的内存块,该内存块必须由先前对 PyObject_Malloc()、PyObject_Realloc() 或 PyObject_Calloc() 的调用返回)。 否则,或者如果
PyObject_Free(p)
之前已被调用,则会发生未定义的行为。如果p为
NULL
,则不执行任何操作。
默认内存分配器
默认内存分配器:
配置 | 名称 | 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 + 调试
|
传奇:
- 名称:PYTHONMALLOC 环境变量的值
malloc
:来自标准 C 库的系统分配器,C 函数:malloc()
、calloc()
、realloc()
和free()
pymalloc
: pymalloc 内存分配器- “+ debug”:使用 PyMem_SetupDebugHooks() 安装的调试钩子
自定义内存分配器
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
功能:
- PYMEM_DOMAIN_RAW
- 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() 分配的缓冲区上调用
在缓冲区开始之前检测写入(缓冲区下溢)
检测缓冲区结束后的写入(缓冲区溢出)
检查
PYMEM_DOMAIN_OBJ
(例如:PyObject_Malloc())和PYMEM_DOMAIN_MEM
(例如:PyMem_Malloc( )) 域被称为
出错时,调试挂钩使用 tracemalloc 模块获取分配内存块的回溯。 仅当 tracemalloc 正在跟踪 Python 内存分配并跟踪内存块时,才会显示回溯。
如果 Python 在调试模式下编译,这些钩子默认安装 [[#default-memory-allocators|]]。 PYTHONMALLOC 环境变量可用于在以发布模式编译的 Python 上安装调试钩子。
3.6 版更改: 此功能现在也适用于在发布模式下编译的 Python。 出错时,调试钩子现在使用 tracemalloc 来获取分配内存块的回溯。 调试钩子现在还会在调用
PYMEM_DOMAIN_OBJ
和PYMEM_DOMAIN_MEM
域的函数时检查 GIL 是否被保留。3.8 版更改:字节模式
0xCB
(CLEANBYTE
)、0xDB
(DEADBYTE
) 和0xFB
(FORBIDDENBYTE
) 已替换为0xCD
、0xDD
和0xFD
以使用与 Windows CRT 调试malloc()
和free()
。
pymalloc 分配器
Python 有一个 pymalloc 分配器,它针对生命周期较短的小对象(小于或等于 512 字节)进行了优化。 它使用称为“arenas”的内存映射,固定大小为 256 KiB。 对于大于 512 字节的分配,它回退到 PyMem_RawMalloc() 和 PyMem_RawRealloc()。
pymalloc 是 PYMEM_DOMAIN_MEM
(例如:PyMem_Malloc())和 PYMEM_DOMAIN_OBJ
(例如:)的 默认分配器 ]PyObject_Malloc()) 域。
arena 分配器使用以下函数:
VirtualAlloc()
和VirtualFree()
在 Windows 上,mmap()
和munmap()
如果可用,malloc()
和free()
否则。
自定义 pymalloc Arena 分配器
3.4 版中的新功能。
- type PyObjectArenaAllocator
用于描述竞技场分配器的结构。 该结构体包含三个字段:
场地
意义
void *ctx
用户上下文作为第一个参数传递
void* alloc(void *ctx, size_t size)
分配一个大小为字节的区域
void free(void *ctx, void *ptr, size_t size)
释放竞技场
- void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)
- 获取竞技场分配器。
- void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)
- 设置竞技场分配器。
tracemalloc C API
3.7 版中的新功能。
- int PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr, size_t size)
在 tracemalloc 模块中跟踪分配的内存块。
成功返回
0
,错误返回-1
(无法分配内存来存储跟踪)。 如果禁用了 tracemalloc,则返回-2
。如果已跟踪内存块,请更新现有跟踪。
- int PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)
在 tracemalloc 模块中取消跟踪已分配的内存块。 如果块未被跟踪,则不执行任何操作。
如果禁用了 tracemalloc,则返回
-2
,否则返回0
。
例子
这是 Overview 部分中的示例,重写后使用第一个函数集从 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 系列,以便将混合不同分配器的风险降至最低。 以下代码序列包含两个错误,其中一个被标记为 fatal,因为它混合了两个在不同堆上运行的不同分配器。
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 中定义和实现新对象类型中进行解释。