內存管理?
概述?
在 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指針。除非 p 是
NULL,否則它必須是之前調用PyMem_RawMalloc()、PyMem_RawRealloc()或PyMem_RawCalloc()所返回的。如果請求失敗,
PyMem_RawRealloc()返回NULL, p 仍然是指向先前內存區域的有效指針。
-
void
PyMem_RawFree(void?*p)? 釋放 p 指向的內存塊。除非 p 是
NULL,否則它必須是之前調用PyMem_RawMalloc()、PyMem_RawRealloc()或PyMem_RawCalloc()所返回的指針。否則,或在PyMem_RawFree(p)之前已經調用過的情況下,未定義的行為會發生。如果 p 是
NULL, 那么什么操作也不會進行。
內存接口?
以下函數集,仿照 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指針。除非 p 是
NULL,否則它必須是之前調用PyMem_Malloc()、PyMem_Realloc()或PyMem_Calloc()所返回的。如果請求失敗,
PyMem_Realloc()返回NULL, p 仍然是指向先前內存區域的有效指針。
-
void
PyMem_Free(void?*p)? 釋放 p 指向的內存塊。除非 p 是
NULL,否則它必須是之前調用PyMem_Malloc()、PyMem_Realloc()或PyMem_Calloc()所返回的指針。否則,或在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 的原始值,以避免在處理錯誤時丟失內存。
-
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堆分配和釋放內存。
警告
在使用這些函數時,必須持有 全局解釋器鎖(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指針。除非 p 是
NULL,否則它必須是之前調用PyObject_Malloc()、PyObject_Realloc()或PyObject_Calloc()所返回的。如果請求失敗,
PyObject_Realloc()返回NULL, p 仍然是指向先前內存區域的有效指針。
-
void
PyObject_Free(void?*p)? 釋放 p 指向的內存塊。除非 p 是
NULL,否則它必須是之前調用PyObject_Malloc()、PyObject_Realloc()或PyObject_Calloc()所返回的指針。否則,或在PyObject_Free(p)之前已經調用過的情況下,未定義的行為會發生。如果 p 是
NULL, 那么什么操作也不會進行。
默認內存分配器?
默認內存分配器:
配置 |
名稱 |
PyMem_RawMalloc |
PyMem_Malloc |
PyObject_Malloc |
|---|---|---|---|---|
發布版本 |
|
|
|
|
調試構建 |
|
|
|
|
沒有 pymalloc 的發布版本 |
|
|
|
|
沒有 pymalloc 的調試構建 |
|
|
|
|
說明:
名稱: 環境變量
PYTHONMALLOC的值malloc: 來自 C 標準庫的系統分配, C 函數malloc(),calloc(),realloc()andfree()pymalloc: pymalloc 內存分配器"+ 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)分配一個初始化為 0 的內存塊
void* realloc(void *ctx, void *ptr, size_t new_size)分配一個內存塊或調整其大小
void free(void *ctx, void *ptr)釋放一個內存塊
在 3.5 版更改: The
PyMemAllocatorstructure was renamed toPyMemAllocatorExand a newcallocfield 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)。運行時檢查:
檢測對 API 的違反,例如:對用
PyMem_Malloc()分配的緩沖區調用PyObject_Free()。檢測緩沖區起始位置前的寫入(緩沖區下溢)。
檢測緩沖區終止位置后的寫入(緩沖區溢出)。
檢測當調用
PYMEM_DOMAIN_OBJ(如:PyObject_Malloc()) 和PYMEM_DOMAIN_MEM(如:PyMem_Malloc()) 域的分配器函數時 GIL 已被保持。
在出錯時,調試鉤子使用
tracemalloc模塊來回溯內存塊被分配的位置。只有當tracemalloc正在追蹤 Python 內存分配,并且內存塊被追蹤時,才會顯示回溯。如果 Python 是在調試模式下編譯的,這些鉤子是 installed by default 。環境變量
PYTHONMALLOC可以用來在發布模式編譯的 Python 上安裝調試鉤子。在 3.6 版更改: 這個函數現在也適用于以 發布模式編譯的 Python。在出錯時,調試鉤子現在使用
tracemalloc來回溯內存塊被分配的位置。調試鉤子現在也檢查當PYMEM_DOMAIN_OBJ和PYMEM_DOMAIN_MEM域的函數被調用時,全局解釋器鎖是否被持有。在 3.7.3 版更改: 字節模式
0xCB(CLEANBYTE)、0xDB(DEADBYTE) 和0xFB(FORBIDDENBYTE) 已被0xCD、0xDD和0xFD替代以使用與 Windows CRT 調試malloc()和free()相同的值。
pymalloc 分配器?
Python 有為具有短生命周期的小對象(小于或等于 512 字節)優化的 pymalloc 分配器。它使用固定大小為 256 KiB 的稱為 "arenas" 的內存映射。對于大于512字節的分配,它回到使用 PyMem_RawMalloc() 和 PyMem_RawRealloc() 。
pymalloc 是 PYMEM_DOMAIN_MEM (例如: PyMem_Malloc()) 和 PYMEM_DOMAIN_OBJ (例如: PyObject_Malloc()) 域的 默認分配器 。
arena 分配器使用以下函數:
Windows 上的
VirtualAlloc()andVirtualFree(),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 中定義和實現新對象類型的下一章中講解。
