我正在阅读Python C extensions 中的内存管理文档,据我所知,似乎没有太多理由使用malloc
而不是PyMem_Malloc
. 假设我想分配一个不会暴露给 Python 源代码并将存储在将被垃圾收集的对象中的数组。有什么理由使用malloc
吗?
3 回答
编辑:混合PyMem_Malloc
和PyObject_Malloc
更正;他们是两个不同的电话。
如果没有PYMALLOC_DEBUG
激活宏,PyMem_Malloc
是 libc 的别名malloc()
,有一种特殊情况:调用PyMem_Malloc
分配零字节将返回非 NULL 指针,而 malloc(zero_bytes) 可能返回 NULL 值或引发系统错误(源代码参考):
/* malloc。注意 nbytes==0 试图返回一个非 NULL 指针,不同的
- 来自所有其他当前活动的指针。这可能是不可能的。*/
pymem.h
此外,头文件上还有一条建议性说明:
切勿将 PyMem_ 的调用与对平台 malloc/realloc/calloc/free 的调用混为一谈。例如,在 Windows 上,不同的 DLL 可能最终使用不同的堆,如果您使用 PyMem_Malloc,您将从 Python DLL 使用的堆中获取内存;如果您直接在自己的扩展程序中释放(),那可能是一场灾难。改用 PyMem_Free 可确保 Python 可以将内存返回到正确的堆。作为另一个示例,在 PYMALLOC_DEBUG 模式下,Python 将对所有 PyMem_ 和 PyObject_ 内存函数的所有调用包装在特殊的调试包装器中,这些包装器将额外的调试信息添加到动态内存块中。系统例程不知道如何处理这些东西,Python 包装器也不知道如何处理系统例程直接获得的原始块。
然后,里面有一些 Python 特定的调优,这个函数不仅用于 C 扩展,还用于运行 Python 程序时的所有动态分配,例如,或:PyMem_Malloc
PyObject_Malloc
100*234
str(100)
10 + 4j
>>> id(10 + 4j)
139721697591440
>>> id(10 + 4j)
139721697591504
>>> id(10 + 4j)
139721697591440
前面的complex()
实例是分配在专用池上的小对象。
小对象(<256 字节)分配非常有效,因为它是从一个 8 字节对齐的块池中完成的,每个块大小都存在一个池。还有用于更大分配的 Pages 和 Arenas 块。PyMem_Malloc
PyObject_Malloc
此对源代码的注释解释了PyObject_Malloc
调用是如何优化的:
/*
* The basic blocks are ordered by decreasing execution frequency,
* which minimizes the number of jumps in the most common cases,
* improves branching prediction and instruction scheduling (small
* block allocations typically result in a couple of instructions).
* Unless the optimizer reorders everything, being too smart...
*/
Pools、Pages 和 Arenas 是旨在减少长期运行的 Python 程序的外部内存碎片的优化。
查看源代码以获取有关 Python 内存内部结构的完整详细文档。
扩展使用 malloc 或其他系统分配器分配内存是完全可以的。对于许多类型的模块来说,这是正常且不可避免的——大多数包装了其他库的模块,这些库本身对 Python 一无所知,当它们在该库中发生时会导致本地分配。(一些库允许您控制分配以防止这种情况发生;大多数库不允许。)
使用 PyMem_Malloc 有一个严重的缺点:使用时需要持有 GIL。在进行 CPU 密集型计算或进行任何可能阻塞的调用(如 I/O)时,本机库通常希望释放 GIL。在分配之前需要锁定 GIL 可能介于非常不方便和性能问题之间。
使用 Python 的包装器进行内存分配允许使用 Python 的内存调试代码。然而,对于 Valgrind 之类的工具,我怀疑它在现实世界中的价值。
如果 API 需要,您将需要使用这些函数;例如,如果向 API 传递了一个必须分配给这些函数的指针,那么它就可以被它们释放。除非有使用它们的明确原因,否则我坚持正常分配。
根据我编写 MATLAB .mex 函数的经验,我认为是否使用 malloc 的最大决定因素是可移植性。假设您有一个仅使用内部 c 数据类型执行大量有用功能的头文件(没有必要的 Python 对象交互,因此使用 malloc 没有问题),您突然意识到您想将该头文件移植到具有与 Python 无关(也许它是一个纯粹用 C 编写的项目),使用 malloc 显然是一个更便携的解决方案。
但是对于纯粹是 Python 扩展的代码,我最初的反应是期望原生 c 函数执行得更快。我没有证据支持这一点:)