Memory Management

Overview

Python 中的内存 Management 涉及一个包含所有 Python 对象和数据结构的私有堆。该私有堆的 Management 由* Python 内存 Management 器*内部确保。 Python 内存 Management 器具有不同的组件,这些组件处理各种动态存储 Management 方面的问题,例如共享,分段,预分配或缓存。

在最低级别上,原始内存分配器pass与 os 的内存 Management 器进行交互,确保私有堆中有足够的空间来存储所有与 Python 相关的数据。在原始内存分配器之上,几个特定于对象的分配器在同一个堆上运行,并实现适用于每种对象类型特性的不同内存 Management 策略。例如,整数对象在堆中的 Management 与字符串,Tuples 或字典的 Management 不同,因为整数意味着不同的存储要求和速度/空间权衡。因此,Python 内存 Management 器将一些工作委托给特定于对象的分配器,但要确保后者在私有堆的范围内运行。

重要的是要理解,Python 堆的 Management 是由解释器本身执行的,并且用户无法对其进行控制,即使它们定期操纵指向该堆内部内存块的对象指针也是如此。 Python 内存 Management 器pass本文档中列出的 Python/C API 函数根据需要执行对 Python 对象和其他内部缓冲区的堆空间分配。

为避免内存损坏,扩展编写程序切勿try使用 C 库所导出的函数malloc()calloc()realloc()free()对 Python 对象进行操作。这将导致 C 分配器和 Python 内存 Management 器之间的混合调用产生致命的后果,因为它们实现不同的算法并在不同的堆上运行。但是,可以出于个人目的使用 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 = PyString_FromString(buf);
free(buf); /* malloc'ed */
return res;

在此示例中,对 I/O 缓冲区的内存请求由 C 库分配器处理。 Python 内存 Management 器仅涉及结果返回的字符串对象的分配。

但是,在大多数情况下,建议特别从 Python 堆中分配内存,因为后者在 Python 内存 Management 器的控制之下。例如,当解释器扩展为用 C 语言编写的新对象类型时,这是必需的。使用 Python 堆的另一个原因是希望通知 Python 内存 Management 器有关扩展模块的内存需求。即使当请求的内存专门用于内部高度特定的目的时,将所有内存请求委派给 Python 内存 Management 器也可以使解释器整体上更准确地了解其内存占用情况。因此,在某些情况下,Python 内存 Management 器可能会或可能不会触发适当的操作,例如垃圾回收,内存压缩或其他预防性过程。请注意,pass使用上一示例中所示的 C 库分配器,为 I/O 缓冲区分配的内存将完全转义 Python 内存 Management 器。

Memory Interface

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

  • 无效* PyMem_Malloc(size_t * n *)

    • 分配* n 个字节,并返回void*类型的指针到已分配的内存,如果请求失败,则返回 NULL 。请求零字节将返回一个唯一的非 NULL *指针(如果可能的话),就像已调用PyMem_Malloc(1)一样。内存将不会以任何方式初始化。
  • void * PyMem_Realloc(void ** p *,size_t * n *)

    • 将* p 指向的存储块的大小调整为 n 个字节。内容将保持不变,直到新旧尺寸的最小值。如果 p NULL ,则调用等效于PyMem_Malloc(n);否则,如果 n 等于零,则将调整内存块的大小,但不会释放该内存块,并且返回的指针为非 NULL 。除非 p NULL ,否则它必须是先前调用PyMem_Malloc()PyMem_Realloc()返回的。如果请求失败,则PyMem_Realloc()返回 NULL ,并且 p *仍然是指向先前存储区的有效指针。
  • 无效PyMem_Free(void ** p *)

    • 释放* p 指向的内存块,该内存块必须是先前调用PyMem_Malloc()PyMem_Realloc()返回的。否则,或者如果以前已调用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 的原始值,以避免在处理错误时丢失内存。
  • 无效PyMem_Del(void ** p *)

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

PyMem_MALLOC() , PyMem_REALLOC() , PyMem_FREE() .

PyMem_NEW() , PyMem_RESIZE() , PyMem_DEL() .

Object allocators

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

默认情况下,这些Function使用pymalloc 内存分配器

Warning

使用这些Function时,必须按住GIL

  • 无效* PyObject_Malloc(size_t * n *)
    • 分配* n 个字节,并返回void*类型的指针到已分配的内存,如果请求失败,则返回 NULL *。

请求零字节将在可能的情况下返回一个不同的非* NULL *指针,就好像已调用PyObject_Malloc(1)一样。内存将不会以任何方式初始化。

  • void * PyObject_Realloc(void ** p *,size_t * n *)
    • 将* p 指向的存储块的大小调整为 n *个字节。内容将保持不变,直到新旧尺寸的最小值。

如果* p NULL ,则调用等效于PyObject_Malloc(n);否则,如果 n 等于零,则将调整内存块的大小,但不会释放该内存块,并且返回的指针为非 NULL *。

除非* p NULL *,否则它必须是先前调用PyObject_Malloc()PyObject_Realloc()PyObject_Calloc()返回的。

如果请求失败,则PyObject_Realloc()返回* NULL ,并且 p *仍然是指向先前存储区的有效指针。

  • 无效PyObject_Free(void ** p *)
    • 释放* p *指向的内存块,该内存块必须是先前调用PyObject_Malloc()PyObject_Realloc()PyObject_Calloc()返回的。否则,或者如果以前已调用PyObject_Free(p),则会发生未定义的行为。

如果* p NULL *,则不执行任何操作。

此外,提供了以下宏集:

pymalloc 分配器

Python 具有一个* pymalloc *分配器,该分配器针对寿命较短的小型对象(小于或等于 512 字节)进行了优化。它使用称为“ arenas”的内存 Map,固定大小为 256 KiB。对于大于 512 字节的分配,它回落到malloc()realloc()

竞技场分配器使用以下Function:

  • mmap()munmap()(如果有),

  • malloc()free()否则。

在版本 2.7.7 中更改:阈值从 256 字节更改为 512 字节。现在,竞技场分配器使用mmap()(如果有)。

Examples

这是第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 = PyString_FromString(buf);
PyMem_Free(buf); /* allocated with PyMem_Malloc */
return res;

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

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 = PyString_FromString(buf);
PyMem_Del(buf); /* allocated with PyMem_New */
return res;

请注意,在上面的两个示例中,总是pass属于同一集合的函数来操纵缓冲区。实际上,对于给定的内存块,需要使用相同的内存 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 堆的原始内存块的Function外,还使用PyObject_New()PyObject_NewVar()PyObject_Del()分配和释放 Python 中的对象。

这些将在下一章有关在 C 中定义和实现新对象类型的说明中进行解释。