內存管理?

概述?

在 Python 中,內存管理涉及到一個(gè)包含所有 Python 對象和數據結構的私有堆(heap)。這個(gè)私有堆的管理由內部的 Python 內存管理器(Python memory manager) 保證。Python 內存管理器有不同的組件來(lái)處理各種動(dòng)態(tài)存儲管理方面的問(wèn)題,如共享、分割、預分配或緩存。

在最底層,一個(gè)原始內存分配器通過(guò)與操作系統的內存管理器交互,確保私有堆中有足夠的空間來(lái)存儲所有與 Python 相關(guān)的數據。在原始內存分配器的基礎上,幾個(gè)對象特定的分配器在同一堆上運行,并根據每種對象類(lèi)型的特點(diǎn)實(shí)現不同的內存管理策略。例如,整數對象在堆內的管理方式不同于字符串、元組或字典,因為整數需要不同的存儲需求和速度與空間的權衡。因此,Python 內存管理器將一些工作分配給對象特定分配器,但確保后者在私有堆的范圍內運行。

Python 堆內存的管理是由解釋器來(lái)執行,用戶(hù)對它沒(méi)有控制權,即使他們經(jīng)常操作指向堆內內存塊的對象指針,理解這一點(diǎn)十分重要。Python 對象和其他內部緩沖區的堆空間分配是由 Python 內存管理器按需通過(guò)本文檔中列出的 Python/C API 函數進(jìn)行的。

為了避免內存破壞,擴展的作者永遠不應該試圖用 C 庫函數導出的函數來(lái)對 Python 對象進(jìn)行操作,這些函數包括: malloc(), calloc(), realloc()free()。這將導致 C 分配器和 Python 內存管理器之間的混用,引發(fā)嚴重后果,這是由于它們實(shí)現了不同的算法,并在不同的堆上操作。但是,我們可以安全地使用 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;

在這個(gè)例子中,I/O 緩沖區的內存請求是由 C 庫分配器處理的。Python 內存管理器只參與了分配作為結果返回的字節對象。

然而,在大多數情況下,建議專(zhuān)門(mén)從 Python 堆中分配內存,因為后者由 Python 內存管理器控制。例如,當解釋器擴展了用 C 寫(xiě)的新對象類(lèi)型時(shí),就必須這樣做。使用 Python 堆的另一個(gè)原因是希望*通知* Python 內存管理器關(guān)于擴展模塊的內存需求。即使所請求的內存全部只用于內部的、高度特定的目的,將所有的內存請求交給 Python 內存管理器能讓解釋器對其內存占用的整體情況有更準確的了解。因此,在某些情況下,Python 內存管理器可能會(huì )觸發(fā)或不觸發(fā)適當的操作,如垃圾回收、內存壓縮或其他預防性操作。請注意,通過(guò)使用前面例子中所示的 C 庫分配器,為 I/O 緩沖區分配的內存會(huì )完全不受 Python 內存管理器管理。

參見(jiàn)

環(huán)境變量 PYTHONMALLOC 可被用來(lái)配置 Python 所使用的內存分配器。

環(huán)境變量 PYTHONMALLOCSTATS 可以用來(lái)在每次創(chuàng )建和關(guān)閉新的 pymalloc 對象區域時(shí)打印 pymalloc 內存分配器 的統計數據。

Allocator Domains?

All allocating functions belong to one of three different "domains" (see also PyMemAllocatorDomain). These domains represent different allocation strategies and are optimized for different purposes. The specific details on how every domain allocates memory or what internal functions each domain calls is considered an implementation detail, but for debugging purposes a simplified table can be found at here. There is no hard requirement to use the memory returned by the allocation functions belonging to a given domain for only the purposes hinted by that domain (although this is the recommended practice). For example, one could use the memory returned by PyMem_RawMalloc() for allocating Python objects or the memory returned by PyObject_Malloc() for allocating memory for buffers.

The three allocation domains are:

  • Raw domain: intended for allocating memory for general-purpose memory buffers where the allocation must go to the system allocator or where the allocator can operate without the GIL. The memory is requested directly to the system.

  • "Mem" domain: intended for allocating memory for Python buffers and general-purpose memory buffers where the allocation must be performed with the GIL held. The memory is taken from the Python private heap.

  • Object domain: intended for allocating memory belonging to Python objects. The memory is taken from the Python private heap.

When freeing memory previously allocated by the allocating functions belonging to a given domain,the matching specific deallocating functions must be used. For example, PyMem_Free() must be used to free memory allocated using PyMem_Malloc().

原始內存接口?

以下函數集封裝了系統分配器。這些函數是線(xiàn)程安全的,不需要持有 全局解釋器鎖。

default raw memory allocator 使用這些函數:malloc()、 calloc()、 realloc()free();申請零字節時(shí)則調用 malloc(1) (或 calloc(1, 1)

3.4 新版功能.

void *PyMem_RawMalloc(size_t n)?

分配 n 個(gè)字節并返回一個(gè)指向分配的內存的 void* 類(lèi)型指針,如果請求失敗則返回 NULL。

請求零字節可能返回一個(gè)獨特的非 NULL 指針,就像調用了 PyMem_RawMalloc(1) 一樣。但是內存不會(huì )以任何方式被初始化。

void *PyMem_RawCalloc(size_t nelem, size_t elsize)?

分配 nelem 個(gè)元素,每個(gè)元素的大小為 elsize 字節,并返回指向分配的內存的 void* 類(lèi)型指針,如果請求失敗則返回 NULL。 內存會(huì )被初始化為零。

請求零字節可能返回一個(gè)獨特的非 NULL 指針,就像調用了 PyMem_RawCalloc(1, 1) 一樣。

3.5 新版功能.

void *PyMem_RawRealloc(void *p, size_t n)?

p 指向的內存塊大小調整為 n 字節。以新舊內存塊大小中的最小值為準,其中內容保持不變,

如果 pNULL ,則相當于調用 PyMem_RawMalloc(n) ;如果 n 等于 0,則內存塊大小會(huì )被調整,但不會(huì )被釋放,返回非 NULL 指針。

除非 pNULL ,否則它必須是之前調用 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) 之前已經(jīng)調用過(guò)的情況下,未定義的行為會(huì )發(fā)生。

如果 pNULL, 那么什么操作也不會(huì )進(jìn)行。

內存接口?

以下函數集,仿照 ANSI C 標準,并指定了請求零字節時(shí)的行為,可用于從Python堆分配和釋放內存。

默認內存分配器 使用了 pymalloc 內存分配器.

警告

在使用這些函數時(shí),必須持有 全局解釋器鎖(GIL) 。

在 3.6 版更改: 現在默認的分配器是 pymalloc 而非系統的 malloc() 。

void *PyMem_Malloc(size_t n)?
Part of the Stable ABI.

分配 n 個(gè)字節并返回一個(gè)指向分配的內存的 void* 類(lèi)型指針,如果請求失敗則返回 NULL。

請求零字節可能返回一個(gè)獨特的非 NULL 指針,就像調用了 PyMem_Malloc(1) 一樣。但是內存不會(huì )以任何方式被初始化。

void *PyMem_Calloc(size_t nelem, size_t elsize)?
Part of the Stable ABI since version 3.7.

分配 nelem 個(gè)元素,每個(gè)元素的大小為 elsize 字節,并返回指向分配的內存的 void* 類(lèi)型指針,如果請求失敗則返回 NULL。 內存會(huì )被初始化為零。

請求零字節可能返回一個(gè)獨特的非 NULL 指針,就像調用了 PyMem_Calloc(1, 1) 一樣。

3.5 新版功能.

void *PyMem_Realloc(void *p, size_t n)?
Part of the Stable ABI.

p 指向的內存塊大小調整為 n 字節。以新舊內存塊大小中的最小值為準,其中內容保持不變,

如果 pNULL ,則相當于調用 PyMem_Malloc(n) ;如果 n 等于 0,則內存塊大小會(huì )被調整,但不會(huì )被釋放,返回非 NULL 指針。

除非 pNULL ,否則它必須是之前調用 PyMem_Malloc() 、 PyMem_Realloc()PyMem_Calloc() 所返回的。

如果請求失敗,PyMem_Realloc() 返回 NULL , p 仍然是指向先前內存區域的有效指針。

void PyMem_Free(void *p)?
Part of the Stable ABI.

釋放 p 指向的內存塊。 p 必須是之前調用 PyMem_Malloc() 、 PyMem_Realloc()PyMem_Calloc() 所返回的指針。否則,或在 PyMem_Free(p) 之前已經(jīng)調用過(guò)的情況下,未定義的行為會(huì )發(fā)生。

如果 pNULL, 那么什么操作也不會(huì )進(jìn)行。

以下面向類(lèi)型的宏為方便而提供。 注意 TYPE 可以指任何 C 類(lèi)型。

TYPE *PyMem_New(TYPE, size_t n)?

PyMem_Malloc() 相同,但會(huì )分配 (n * sizeof(TYPE)) 字節的內存。 返回一個(gè)轉換為 TYPE* 的指針。 內存將不會(huì )以任何方式被初始化。

TYPE *PyMem_Resize(void *p, TYPE, size_t n)?

PyMem_Realloc() 相同,但內存塊的大小被調整為 (n * sizeof(TYPE)) 字節。 返回一個(gè)轉換為 TYPE* 類(lèi)型的指針。 返回時(shí),p 將為指向新內存區域的指針,如果失敗則返回 NULL。

這是一個(gè) C 預處理宏, p 總是被重新賦值。請保存 p 的原始值,以避免在處理錯誤時(shí)丟失內存。

void PyMem_Del(void *p)?

PyMem_Free() 相同

此外,我們還提供了以下宏集用于直接調用 Python 內存分配器,而不涉及上面列出的 C API 函數。但是請注意,使用它們并不能保證跨 Python 版本的二進(jìn)制兼容性,因此在擴展模塊被棄用。

  • PyMem_MALLOC(size)

  • PyMem_NEW(type, size)

  • PyMem_REALLOC(ptr, size)

  • PyMem_RESIZE(ptr, type, size)

  • PyMem_FREE(ptr)

  • PyMem_DEL(ptr)

對象分配器?

以下函數集,仿照 ANSI C 標準,并指定了請求零字節時(shí)的行為,可用于從Python堆分配和釋放內存。

備注

There is no guarantee that the memory returned by these allocators can be successfully cast to a Python object when intercepting the allocating functions in this domain by the methods described in the Customize Memory Allocators section.

默認對象分配器 使用 pymalloc 內存分配器.

警告

在使用這些函數時(shí),必須持有 全局解釋器鎖(GIL) 。

void *PyObject_Malloc(size_t n)?
Part of the Stable ABI.

分配 n 個(gè)字節并返回一個(gè)指向分配的內存的 void* 類(lèi)型指針,如果請求失敗則返回 NULL。

請求零字節可能返回一個(gè)獨特的非 NULL 指針,就像調用了 PyObject_Malloc(1) 一樣。但是內存不會(huì )以任何方式被初始化。

void *PyObject_Calloc(size_t nelem, size_t elsize)?
Part of the Stable ABI since version 3.7.

分配 nelem 個(gè)元素,每個(gè)元素的大小為 elsize 字節,并返回指向分配的內存的 void* 類(lèi)型指針,如果請求失敗則返回 NULL。 內存會(huì )被初始化為零。

請求零字節可能返回一個(gè)獨特的非 NULL 指針,就像調用了 PyObject_Calloc(1, 1) 一樣。

3.5 新版功能.

void *PyObject_Realloc(void *p, size_t n)?
Part of the Stable ABI.

p 指向的內存塊大小調整為 n 字節。以新舊內存塊大小中的最小值為準,其中內容保持不變,

如果*p*是``NULL``,則相當于調用 PyObject_Malloc(n) ;如果 n 等于 0,則內存塊大小會(huì )被調整,但不會(huì )被釋放,返回非 NULL 指針。

除非 pNULL ,否則它必須是之前調用 PyObject_Malloc() 、 PyObject_Realloc()PyObject_Calloc() 所返回的。

如果請求失敗,PyObject_Realloc() 返回 NULL , p 仍然是指向先前內存區域的有效指針。

void PyObject_Free(void *p)?
Part of the Stable ABI.

釋放 p 指向的內存塊。 p 必須是之前調用 PyObject_Malloc() 、 PyObject_Realloc()PyObject_Calloc() 所返回的指針。否則,或在 PyObject_Free(p) 之前已經(jīng)調用過(guò)的情況下,未定義行為會(huì )發(fā)生。

如果 pNULL, 那么什么操作也不會(huì )進(jìn)行。

默認內存分配器?

默認內存分配器:

配置

名稱(chēng)

PyMem_RawMalloc

PyMem_Malloc

PyObject_Malloc

發(fā)布版本

"pymalloc"

malloc

pymalloc

pymalloc

調試構建

"pymalloc_debug"

malloc + debug

pymalloc + debug

pymalloc + debug

沒(méi)有 pymalloc 的發(fā)布版本

"malloc"

malloc

malloc

malloc

沒(méi)有 pymalloc 的調試構建

"malloc_debug"

malloc + debug

malloc + debug

malloc + debug

說(shuō)明:

自定義內存分配器?

3.4 新版功能.

type PyMemAllocatorEx?

Structure used to describe a memory block allocator. The structure has the following fields:

含意

void *ctx

作為第一個(gè)參數傳入的用戶(hù)上下文

void* malloc(void *ctx, size_t size)

分配一個(gè)內存塊

void* calloc(void *ctx, size_t nelem, size_t elsize)

分配一個(gè)初始化為 0 的內存塊

void* realloc(void *ctx, void *ptr, size_t new_size)

分配一個(gè)內存塊或調整其大小

void free(void *ctx, void *ptr)

釋放一個(gè)內存塊

在 3.5 版更改: The PyMemAllocator structure was renamed to PyMemAllocatorEx and a new calloc field was added.

type PyMemAllocatorDomain?

用來(lái)識別分配器域的枚舉類(lèi)。域有:

PYMEM_DOMAIN_RAW?

函數

PYMEM_DOMAIN_MEM?

函數

PYMEM_DOMAIN_OBJ?

函數

void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)?

獲取指定域的內存塊分配器。

void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)?

設置指定域的內存塊分配器。

當請求零字節時(shí),新的分配器必須返回一個(gè)獨特的非 NULL 指針。

對于 PYMEM_DOMAIN_RAW 域,分配器必須是線(xiàn)程安全的:當分配器被調用時(shí),不持有 全局解釋器鎖 。

如果新的分配器不是鉤子(不調用之前的分配器),必須調用 PyMem_SetupDebugHooks() 函數在新分配器上重新安裝調試鉤子。

See also PyPreConfig.allocator and Preinitialize Python with PyPreConfig.

void PyMem_SetupDebugHooks(void)?

Setup debug hooks in the Python memory allocators to detect memory errors.

Debug hooks on the Python memory allocators?

When Python is built in debug mode, the PyMem_SetupDebugHooks() function is called at the Python preinitialization to setup debug hooks on Python memory allocators to detect memory errors.

The PYTHONMALLOC environment variable can be used to install debug hooks on a Python compiled in release mode (ex: PYTHONMALLOC=debug).

The PyMem_SetupDebugHooks() function can be used to set debug hooks after calling PyMem_SetAllocator().

These debug hooks fill dynamically allocated memory blocks with special, recognizable bit patterns. Newly allocated memory is filled with the byte 0xCD (PYMEM_CLEANBYTE), freed memory is filled with the byte 0xDD (PYMEM_DEADBYTE). Memory blocks are surrounded by "forbidden bytes" filled with the byte 0xFD (PYMEM_FORBIDDENBYTE). Strings of these bytes are unlikely to be valid addresses, floats, or ASCII strings.

運行時(shí)檢查:

  • Detect API violations. For example, detect if PyObject_Free() is called on a memory block allocated by PyMem_Malloc().

  • Detect write before the start of the buffer (buffer underflow).

  • Detect write after the end of the buffer (buffer overflow).

  • Check that the GIL is held when allocator functions of PYMEM_DOMAIN_OBJ (ex: PyObject_Malloc()) and PYMEM_DOMAIN_MEM (ex: PyMem_Malloc()) domains are called.

在出錯時(shí),調試鉤子使用 tracemalloc 模塊來(lái)回溯內存塊被分配的位置。只有當 tracemalloc 正在追蹤 Python 內存分配,并且內存塊被追蹤時(shí),才會(huì )顯示回溯。

Let S = sizeof(size_t). 2*S bytes are added at each end of each block of N bytes requested. The memory layout is like so, where p represents the address returned by a malloc-like or realloc-like function (p[i:j] means the slice of bytes from *(p+i) inclusive up to *(p+j) exclusive; note that the treatment of negative indices differs from a Python slice):

p[-2*S:-S]

Number of bytes originally asked for. This is a size_t, big-endian (easier to read in a memory dump).

p[-S]

API identifier (ASCII character):

  • 'r' for PYMEM_DOMAIN_RAW.

  • 'm' for PYMEM_DOMAIN_MEM.

  • 'o' for PYMEM_DOMAIN_OBJ.

p[-S+1:0]

Copies of PYMEM_FORBIDDENBYTE. Used to catch under- writes and reads.

p[0:N]

The requested memory, filled with copies of PYMEM_CLEANBYTE, used to catch reference to uninitialized memory. When a realloc-like function is called requesting a larger memory block, the new excess bytes are also filled with PYMEM_CLEANBYTE. When a free-like function is called, these are overwritten with PYMEM_DEADBYTE, to catch reference to freed memory. When a realloc- like function is called requesting a smaller memory block, the excess old bytes are also filled with PYMEM_DEADBYTE.

p[N:N+S]

Copies of PYMEM_FORBIDDENBYTE. Used to catch over- writes and reads.

p[N+S:N+2*S]

Only used if the PYMEM_DEBUG_SERIALNO macro is defined (not defined by default).

A serial number, incremented by 1 on each call to a malloc-like or realloc-like function. Big-endian size_t. If "bad memory" is detected later, the serial number gives an excellent way to set a breakpoint on the next run, to capture the instant at which this block was passed out. The static function bumpserialno() in obmalloc.c is the only place the serial number is incremented, and exists so you can set such a breakpoint easily.

A realloc-like or free-like function first checks that the PYMEM_FORBIDDENBYTE bytes at each end are intact. If they've been altered, diagnostic output is written to stderr, and the program is aborted via Py_FatalError(). The other main failure mode is provoking a memory error when a program reads up one of the special bit patterns and tries to use it as an address. If you get in a debugger then and look at the object, you're likely to see that it's entirely filled with PYMEM_DEADBYTE (meaning freed memory is getting used) or PYMEM_CLEANBYTE (meaning uninitialized memory is getting used).

在 3.6 版更改: The PyMem_SetupDebugHooks() function now also works on Python compiled in release mode. On error, the debug hooks now use tracemalloc to get the traceback where a memory block was allocated. The debug hooks now also check if the GIL is held when functions of PYMEM_DOMAIN_OBJ and PYMEM_DOMAIN_MEM domains are called.

在 3.8 版更改: Byte patterns 0xCB (PYMEM_CLEANBYTE), 0xDB (PYMEM_DEADBYTE) and 0xFB (PYMEM_FORBIDDENBYTE) have been replaced with 0xCD, 0xDD and 0xFD to use the same values than Windows CRT debug malloc() and free().

pymalloc 分配器?

Python 有為具有短生命周期的小對象(小于或等于 512 字節)優(yōu)化的 pymalloc 分配器。它使用固定大小為 256 KiB 的稱(chēng)為 "arenas" 的內存映射。對于大于512字節的分配,它回到使用 PyMem_RawMalloc()PyMem_RawRealloc() 。

pymallocPYMEM_DOMAIN_MEM (例如: PyMem_Malloc()) 和 PYMEM_DOMAIN_OBJ (例如: PyObject_Malloc()) 域的 默認分配器 。

arena 分配器使用以下函數:

  • Windows 上的 VirtualAlloc()VirtualFree() ,

  • mmap()munmap() ,如果可用,

  • 否則, malloc()free() 。

This allocator is disabled if Python is configured with the --without-pymalloc option. It can also be disabled at runtime using the PYTHONMALLOC environment variable (ex: PYTHONMALLOC=malloc).

自定義 pymalloc Arena 分配器?

3.4 新版功能.

type PyObjectArenaAllocator?

用來(lái)描述一個(gè) arena 分配器的結構體。這個(gè)結構體有三個(gè)字段:

含意

void *ctx

作為第一個(gè)參數傳入的用戶(hù)上下文

void* alloc(void *ctx, size_t size)

分配一塊 size 字節的區域

void free(void *ctx, void *ptr, size_t size)

釋放一塊區域

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 模塊中跟蹤一個(gè)已分配的內存塊。

成功時(shí)返回 0,出錯時(shí)返回 -1 (無(wú)法分配內存來(lái)保存跟蹤信息)。 如果禁用了 tracemalloc 則返回 -2。

如果內存塊已被跟蹤,則更新現有跟蹤信息。

int PyTraceMalloc_Untrack(unsigned int domain, uintptr_t ptr)?

tracemalloc 模塊中取消跟蹤一個(gè)已分配的內存塊。 如果內存塊未被跟蹤則不執行任何操作。

如果 tracemalloc 被禁用則返回 -2,否則返回 0。

例子?

以下是來(lái)自 概述 小節的示例,經(jīng)過(guò)重寫(xiě)以使 I/O 緩沖區是通過(guò)使用第一個(gè)函數集從 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;

使用面向類(lèi)型函數集的相同代碼:

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;

請注意在以上兩個(gè)示例中,緩沖區總是通過(guò)歸屬于相同集的函數來(lái)操縱的。 事實(shí)上,對于一個(gè)給定的內存塊必須使用相同的內存 API 族,以便使得混合不同分配器的風(fēng)險減至最低。 以下代碼序列包含兩處錯誤,其中一個(gè)被標記為 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()  */

除了旨在處理來(lái)自 Python 堆的原始內存塊的函數之外, Python 中的對象是通過(guò) PyObject_New(), PyObject_NewVar()PyObject_Del() 來(lái)分配和釋放的。

這些將在有關(guān)如何在 C 中定義和實(shí)現新對象類(lèi)型的下一章中講解。