内存管理¶
概述¶
在 Python 中,内存管理涉及到一个包含所有 Python 对象和数据结构的私有堆(heap)。这个私有堆的管理由内部的 Python 内存管理器(Python memory manager) 保证。Python 内存管理器有不同的组件来处理各种动态存储管理方面的问题,如共享、分割、预分配或缓存。
在最底层,一个原始内存分配器通过与操作系统的内存管理器交互,确保私有堆中有足够的空间来存储所有与 Python 相关的数据。在原始内存分配器的基础上,几个对象特定的分配器在同一堆上运行,并根据每种对象类型的特点实现不同的内存管理策略。例如,整数对象在堆内的管理方式不同于字符串、元组或字典,因为整数需要不同的存储需求和速度与空间的权衡。因此,Python 内存管理器将一些工作分配给对象特定分配器,但确保后者在私有堆的范围内运行。
Python 堆内存的管理是由解释器来执行,用户对它没有控制权,即使他们经常操作指向堆内内存块的对象指针,理解这一点十分重要。Python 对象和其他内部缓冲区的堆空间分配是由 Python 内存管理器按需通过本文档中列出的 Python/C API 函数进行的。
为了避免内存破坏,扩展的作者永远不应该试图用 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 对象区域时打印 pymalloc 内存分配器 的统计数据。
原始内存接口¶
以下函数集封装了系统分配器。这些函数是线程安全的,不需要持有 全局解释器锁。
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)¶ 分配 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_RawCalloc()
所返回的。如果请求失败,
PyMem_RawRealloc()
返回NULL
, p 仍然是指向先前内存区域的有效指针。
-
void
PyMem_RawFree
(void *p)¶ 释放 p 指向的内存块。 p 必须是之前调用
PyMem_RawMalloc()
、PyMem_RawRealloc()
或PyMem_RawCalloc()
所返回的指针。否则,或在PyMem_RawFree(p)
之前已经调用过的情况下,未定义的行为会发生。如果 p 是
NULL
, 那么什么操作也不会进行。
内存接口¶
以下函数集,仿照 ANSI C 标准,并指定了请求零字节时的行为,可用于从Python堆分配和释放内存。
默认内存分配器 使用了 pymalloc 内存分配器.
警告
在使用这些函数时,必须持有 全局解释器锁(GIL) 。
在 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_Calloc()
所返回的。如果请求失败,
PyMem_Realloc()
返回NULL
, p 仍然是指向先前内存区域的有效指针。
-
void
PyMem_Free
(void *p)¶ 释放 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堆分配和释放内存。
警告
在使用这些函数时,必须持有 全局解释器锁(GIL) 。
-
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 指向的内存块。 p 必须是之前调用
PyObject_Malloc()
、PyObject_Realloc()
或PyObject_Calloc()
所返回的指针。否则,或在PyObject_Free(p)
之前已经调用过的情况下,未定义行为会发生。如果 p 是
NULL
, 那么什么操作也不会进行。
默认内存分配器¶
默认内存分配器:
配置 |
名称 |
PyMem_RawMalloc |
PyMem_Malloc |
PyObject_Malloc |
---|---|---|---|---|
发布版本 |
|
|
|
|
调试构建 |
|
|
|
|
没有 pymalloc 的发布版本 |
|
|
|
|
没有 pymalloc 的调试构建 |
|
|
|
|
说明:
名称: 环境变量
PYTHONMALLOC
的值malloc
: 来自 C 标准库的系统分配, C 函数malloc()
,calloc()
,realloc()
andfree()
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)
分配一个初始化为 0 的内存块
void* realloc(void *ctx, void *ptr, size_t new_size)
分配一个内存块或调整其大小
void free(void *ctx, void *ptr)
释放一个内存块
在 3.5 版更改: The
PyMemAllocator
structure was renamed toPyMemAllocatorEx
and a newcalloc
field was added.
-
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
域,分配器必须是线程安全的:当分配器被调用时,不持有 全局解释器锁 。如果新的分配器不是钩子(不调用之前的分配器),必须调用
PyMem_SetupDebugHooks()
函数在新分配器上重新安装调试钩子。
-
void
PyMem_SetupDebugHooks
(void)¶ 设置检测 Python 内存分配器函数中错误的钩子。
新分配的内存由字节
0xCD
(CLEANBYTE
) 填充,释放的内存由字节0xDD
(DEADBYTE
)填充。内存块被 "禁止字节" 包围(FORBIDDENBYTE
:字节0xFD
)。运行时检查:
检测对 API 的违反,例如:对用
PyMem_Malloc()
分配的缓冲区调用PyObject_Free()
。检测缓冲区起始位置前的写入(缓冲区下溢)。
检测缓冲区终止位置后的写入(缓冲区溢出)。
检测当调用
PYMEM_DOMAIN_OBJ
(如:PyObject_Malloc()
) 和PYMEM_DOMAIN_MEM
(如:PyMem_Malloc()
) 域的分配器函数时 GIL 已被保持。
在出错时,调试钩子使用
tracemalloc
模块来回溯内存块被分配的位置。只有当tracemalloc
正在追踪 Python 内存分配,并且内存块被追踪时,才会显示回溯。如果 Python 是在调试模式下编译的,这些钩子是 installed by default 。环境变量
PYTHONMALLOC
可以用来在发布模式编译的 Python 上安装调试钩子。在 3.6 版更改: 这个函数现在也适用于以 发布模式编译的 Python。在出错时,调试钩子现在使用
tracemalloc
来回溯内存块被分配的位置。调试钩子现在也检查当PYMEM_DOMAIN_OBJ
和PYMEM_DOMAIN_MEM
域的函数被调用时,全局解释器锁是否被持有。在 3.8 版更改: 字节模式
0xCB
(CLEANBYTE
)、0xDB
(DEADBYTE
) 和0xFB
(FORBIDDENBYTE
) 已被0xCD
、0xDD
和0xFD
替代以使用与 Windows CRT 调试malloc()
和free()
相同的值。
pymalloc 分配器¶
Python 有为具有短生命周期的小对象(小于或等于 512 字节)优化的 pymalloc 分配器。它使用固定大小为 256 KiB 的称为 "arenas" 的内存映射。对于大于512字节的分配,它回到使用 PyMem_RawMalloc()
和 PyMem_RawRealloc()
。
pymalloc 是 PYMEM_DOMAIN_MEM
(例如: PyMem_Malloc()
) 和 PYMEM_DOMAIN_OBJ
(例如: PyObject_Malloc()
) 域的 默认分配器 。
arena 分配器使用以下函数:
Windows 上的
VirtualAlloc()
和VirtualFree()
,mmap()
和munmap()
,如果可用,否则,
malloc()
和free()
。
自定义 pymalloc Arena 分配器¶
3.4 新版功能.
-
type
PyObjectArenaAllocator
¶ 用来描述一个 arena 分配器的结构体。这个结构体有三个字段:
域
含义
void *ctx
作为第一个参数传入的用户上下文
void* alloc(void *ctx, size_t size)
分配一块 size 字节的区域
void free(void *ctx, size_t size, void *ptr)
释放一块区域
-
void
PyObject_GetArenaAllocator
(PyObjectArenaAllocator *allocator)¶ 获取 arena 分配器
-
void
PyObject_SetArenaAllocator
(PyObjectArenaAllocator *allocator)¶ 设置 arena 分配器
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
。
示例¶
以下是来自 概述 小节的示例,经过重写以使 I/O 缓冲区是通过使用第一个函数集从 Python 堆中分配的:
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 中定义和实现新对象类型的下一章中讲解。