內存管理?

概述?

在 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 內存分配器 的統計數據。

原始內存接口?

以下函數集封裝了系統分配器。這些函數是線程安全的,不需要持有 GIL

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 指針。

除非 pNULL ,否則它必須是之前調用 PyMem_RawMalloc()PyMem_RawRealloc()PyMem_RawCalloc() 所返回的。

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

void PyMem_RawFree(void?*p)?

釋放 p 指向的內存塊。除非 pNULL ,否則它必須是之前調用 PyMem_RawMalloc()PyMem_RawRealloc()PyMem_RawCalloc() 所返回的指針。否則,或在 PyMem_RawFree(p) 之前已經調用過的情況下,未定義的行為會發生。

如果 pNULL, 那么什么操作也不會進行。

內存接口?

以下函數集,仿照 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 指針。

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

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

void PyMem_Free(void?*p)?

釋放 p 指向的內存塊。除非 pNULL ,否則它必須是之前調用 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

這是一個 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堆分配和釋放內存。

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

警告

在使用這些函數時,必須持有 全局解釋器鎖(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 指針。

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

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

void PyObject_Free(void?*p)?

釋放 p 指向的內存塊。除非 pNULL ,否則它必須是之前調用 PyObject_Malloc()PyObject_Realloc()PyObject_Calloc() 所返回的指針。否則,或在 PyObject_Free(p) 之前已經調用過的情況下,未定義的行為會發生。

如果 pNULL, 那么什么操作也不會進行。

默認內存分配器?

默認內存分配器:

配置

名稱

PyMem_RawMalloc

PyMem_Malloc

PyObject_Malloc

發布版本

"pymalloc"

malloc

pymalloc

pymalloc

調試構建

"pymalloc_debug"

malloc + debug

pymalloc + debug

pymalloc + debug

沒有 pymalloc 的發布版本

"malloc"

malloc

malloc

malloc

沒有 pymalloc 的調試構建

"malloc_debug"

malloc + debug

malloc + debug

malloc + debug

說明:

自定義內存分配器?

3.4 新版功能.

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 to PyMemAllocatorEx and a new calloc field was added.

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 )。

運行時檢查:

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

如果 Python 是在調試模式下編譯的,這些鉤子是 installed by default 。環境變量 PYTHONMALLOC 可以用來在發布模式編譯的 Python 上安裝調試鉤子。

在 3.6 版更改: 這個函數現在也適用于以 發布模式編譯的 Python。在出錯時,調試鉤子現在使用 tracemalloc 來回溯內存塊被分配的位置。調試鉤子現在也檢查當 PYMEM_DOMAIN_OBJPYMEM_DOMAIN_MEM 域的函數被調用時,全局解釋器鎖是否被持有。

在 3.7.3 版更改: 字節模式 0xCB (CLEANBYTE)、 0xDB (DEADBYTE) 和 0xFB (FORBIDDENBYTE) 已被 0xCD0xDD0xFD 替代以使用與 Windows CRT 調試 malloc()free() 相同的值。

pymalloc 分配器?

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

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

arena 分配器使用以下函數:

  • Windows 上的 VirtualAlloc() and VirtualFree() ,

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

  • 否則, malloc()free()

自定義 pymalloc Arena 分配器?

3.4 新版功能.

PyObjectArenaAllocator?

用來描述一個 arena 分配器的結構體。這個結構體有三個字段:

含義

void *ctx

作為第一個參數傳入的用戶上下文

void* alloc(void *ctx, size_t size)

分配一塊 size 字節的區域

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

釋放一塊區域

PyObject_GetArenaAllocator(PyObjectArenaAllocator?*allocator)?

獲取 arena 分配器

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 中定義和實現新對象類型的下一章中講解。