内存管理

概述

Python中的内存管理涉及包含所有Python对象和数据结构的私有堆。//私有堆的管理由Python memory manager在内部确保。Python内存管理器具有不同的组件,可以处理各种动态存储管理方面,如共享,分段,预分配或缓存.

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

重要的是要理解Python堆的管理由解释器本身执行,并且用户无法控制它,即使它们经常操作对象指针到该堆内的内存块。Python内存管理器通过本文档中列出的Python / CAPI函数按需执行Python对象和其他内部缓冲区的堆空间分配.

为避免内存损坏,扩展编写者不应该尝试操作onPython具有C库导出的函数的对象: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缓冲区的内存请求由Clibrary分配器处理。Python内存管理器只涉及作为结果返回的字符串对象的分配.

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

另见

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

PYTHONMALLOCSTATS环境变量可用于打印统计信息pymalloc内存分配器每次创建一个新的pymalloc对象竞技场,并且在shutdown.

Raw内存接口

以下函数集是系统分配器的包装器。这些功能是线程安全的, GIL 不需要保持.

默认的原始内存分配器使用以下功能:malloc(), calloc(), realloc()free();打电话malloc(1)(要么 calloc(1, 1))请求零字节时

版本3.4.

void * PyMem_RawMalloc为size_t  n
分配nbytes并返回类型为void*到分配的记忆,或NULL如果请求失败了

请求零字节返回一个不同的非_NULL指针,如果可能的话,asif PyMem_RawMalloc(1)被称之为。记忆不会以任何方式初始化.

void * PyMem_RawCalloc size_t  nelem,size_t  elsize
分配nelem元素,每个元素的大小以字节为单位elsize并将类型为void*的指针返回给已分配的内存,如果请求失败则返回NULL。内存初始化为零.

请求零元素或大小为零字节的元素返回一个distinctnon – NULL指针,如果可能的话,好像是PyMem_RawCalloc(1, 1)被称为.

新版本3.5.

void * PyMem_RawRealloc void  *p,size_t  n
调整p指向的内存块的大小n字节。内容将保持不变到新旧尺寸的最小值

如果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,则不执行任何操作.

 

记忆界面

以下函数集以ANSI C标准为模型,但在请求零字节时指定行为,可用于从Python堆分配和释放内存.

默认内存分配器使用 pymalloc内存分配器.

警告

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

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

void * PyMem_Malloc size_t  n
分配n字节并将类型为void*的指针返回给已分配的内存,如果请求失败则返回NULL .

如果可能的话,请求零字节返回一个不同的非NULL指针,asif PyMem_Malloc(1)已被调用。记忆不会以任何方式初始化.

void * PyMem_Calloc size_t  nelem,size_t  elsize
分配nelem元素,每个元素的大小以字节为elsize并将类型为void*的指针返回给分配的内存,或NULL如果请求失败。内存初始化为零.

请求零元素或大小为零字节的元素返回一个distinctnon – NULL指针,如果可能的话,好像PyMem_Calloc(1, 1)已经被调用了.

新版本3.5.

void * PyMem_Realloc void  *p,size_t  n
调整p指向的内存块的大小n字节。内容不会改变到旧尺寸和新尺寸的最小尺寸.

如果pNULL,这个电话相当于PyMem_Malloc(n);否则,如果n等于零,则内存块调整大小但不释放,并且指针不是NULL.

除非pNULL,它必须是先前调用PyMem_Malloc(), PyMem_Realloc()PyMem_Calloc().

返回的。如果请求失败,PyMem_Realloc()返回NULLp stilla指向前一个存储区的有效指针.

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在offailure事件中

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

void PyMem_Del void  *p
与…一样 PyMem_Free().

此外,还提供了以下宏集,用于直接调用Python memoryallocator,而不涉及上面列出的C API函数。但是,请注意它们的使用不会保留Pythonversions之间的二进制兼容性,因此在扩展模块中不推荐使用.

  • PyMem_MALLOC(size)
  • PyMem_NEW(type, size)
  • PyMem_REALLOC(ptr, size)
  • PyMem_RESIZE(ptr, type, size)
  • PyMem_FREE(ptr)
  • PyMem_DEL(ptr)

Object allocators

以下函数集,以ANSI C标准为模型,但指定行为时请求零字节,可用于从Python堆分配和释放内存.

默认对象分配器使用 pymalloc内存分配器.

警告

GIL 使用这些功能时必须握住它们

void* PyObject_Malloc size_t  n
分配n字节并将类型为void*的指针返回给已分配的内存,或NULL如果请求失败.

如果可能的话,请求零字节返回一个不同的非NULL指针,asif PyObject_Malloc(1)已被调用。记忆不会以任何方式初始化.

void * PyObject_Calloc size_t  nelem,size_t  elsize
分配nelem元素,每个元素的大小以字节为单位是elsize并将类型为void*的指针返回到已分配的内存,如果请求失败则返回NULL。内存初始化为零.

请求零元素或大小为零字节的元素返回一个distinctnon – NULL指针,如果可能的话,好像PyObject_Calloc(1, 1)被调用了一样.

版本3.5.

void* PyObject_Realloc void  *p,size_t  n
调整p指向的内存块的大小n字节。内容不会改变到新旧尺寸的最小值.

如果pNULL,这个电话相当于PyObject_Malloc(n);否则,如果n等于零,则内存块被调整大小但没有被释放,而且指针是非NULL.

除非pNULL,它必须是先前调用PyObject_Malloc(), PyObject_Realloc()PyObject_Calloc().

返回的。如果请求失败,PyObject_Realloc()返回NULLp仍然有效指向前一个存储区的指针.

void PyObject_Freevoid  *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 + debug malloc + debug malloc + debug

图例:

  • 名称:PYTHONMALLOC环境变量的值
  • malloc:来自标准C库的系统分配器,C函数:malloc(), calloc(), realloc()free()
  • pymalloc pymalloc memoryallocator
  • “+ debug”:安装调试挂钩PyMem_SetupDebugHooks()

自定义内存分配器

版本3.4.

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字段

PyMemAllocatorDomain
枚举用于标识分配器域。域名:

PYMEM_DOMAIN_RAW
函数:

  • PyMem_RawMalloc()
  • PyMem_RawRealloc()
  • PyMem_RawCalloc()
  • PyMem_RawFree()
PYMEM_DOMAIN_MEM
函数:

  • PyMem_Malloc(),
  • PyMem_Realloc()
  • PyMem_Calloc()
  • PyMem_Free()
PYMEM_DOMAIN_OBJ
函数:

  • PyObject_Malloc()
  • PyObject_Realloc()
  • PyObject_Calloc()
  • PyObject_Free()
void PyMem_GetAllocator PyMemAllocatorDomain  domain,PyMemAllocatorEx  *allocator
获取指定域的内存块分配器.
void PyMem_SetAllocator PyMemAllocatorDomain  domain,PyMemAllocatorEx  *allocator
设置指定域的内存块分配器.

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

对于PYMEM_DOMAIN_RAW域名,分配器必须是安全的: GIL 在调用分配器时不被保持.

如果新的分配器不是钩子(不调用前一个分配器),PyMem_SetupDebugHooks()必须调用函数在新的allocator上重新安装调试钩子

//void PyMem_SetupDebugHooks void
安装钩子来检测Python内存分配器函数中的错误.

新分配的内存填充了字节0xCB,释放的内存充满了字节0xDB.

运行时检查:

  • 检测API违规,例如:PyObject_Free()PyMem_Malloc()
  • 在缓冲区启动前检测写入(缓冲区下溢)
  • 在缓冲区结束后检测写入(缓冲区溢出)
  • 检查 GIL PYMEM_DOMAIN_OBJ(例如:PyObject_Malloc())和PYMEM_DOMAIN_MEM(例如:PyMem_Malloc())域被调用

出错,调试挂钩使用tracemalloc模块获取分配内存块的跟踪。仅在tracemalloc跟踪Python内存分配和跟踪的主题块.

这些挂钩是默认安装如果Python是在调试模式下编译的。PYTHONMALLOC环境变量可用于在发布模式下编译的Python上安装调试钩子.

更改版本3.6:此函数现在也适用于在发布模式下编译的Python。出错,调试钩子现在使用tracemalloc来获取分配内存块的回溯。调试钩子现在也检查当PYMEM_DOMAIN_OBJPYMEM_DOMAIN_MEM域的函数被调用时GIL被保持.

 

pymalloc allocator

Python有pymalloc为小对象(小于或等于512字节)优化的分配器,具有较短的生命周期。它使用称为“arenas”的内存映射,固定大小为256 KiB。对于大于512字节的分配,它会回落到PyMem_RawMalloc()PyMem_RawRealloc()

pymalloc默认分配器PYMEM_DOMAIN_MEM(例如:PyMem_Malloc())和PYMEM_DOMAIN_OBJ(例如:PyObject_Malloc())domains.

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

  • VirtualAlloc()VirtualFree()在Windows上,
  • mmap()munmap()如果可用,
  • malloc()free()否则.

自定义pymalloc Arena Allocator

版本3.4.

PyObjectArenaAllocator
用于描述竞技场分配器的结构。结构hasthree字段:

字段 含义
void *ctx 用户上下文作为第一个参数传递
void* alloc(void *ctx, size_t size) 分配一个大小字节的竞技场
void free(void *ctx, size_t size, void *ptr) 免费竞技场
PyObject_GetArenaAllocatorPyObjectArenaAllocator  *allocator
获得竞技场分配器.
PyObject_SetArenaAllocatorPyObjectArenaAllocator  *allocator
设置竞技场分配器.

tracemalloc C API

版本3.7.

 

例子

以下是概述中的示例,重写以便通过使用第一个函数set:

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()  */

除了旨在处理Pythonheap中的原始内存块的函数之外,Python中的对象也随PyObject_New(),PyObject_NewVar()PyObject_Del().

这些将在下一章中讨论如何在C.中定义和实现新对象类型