內(nèi)存管理?
概述?
在 Python 中,內(nèi)存管理涉及到一個包含所有 Python 對象和數(shù)據(jù)結(jié)構(gòu)的私有堆(heap)。這個私有堆的管理由內(nèi)部的 Python 內(nèi)存管理器(Python memory manager) 保證。Python 內(nèi)存管理器有不同的組件來處理各種動態(tài)存儲管理方面的問題,如共享、分割、預分配或緩存。
在最底層,一個原始內(nèi)存分配器通過與操作系統(tǒng)的內(nèi)存管理器交互,確保私有堆中有足夠的空間來存儲所有與 Python 相關(guān)的數(shù)據(jù)。在原始內(nèi)存分配器的基礎(chǔ)上,幾個對象特定的分配器在同一堆上運行,并根據(jù)每種對象類型的特點實現(xiàn)不同的內(nèi)存管理策略。例如,整數(shù)對象在堆內(nèi)的管理方式不同于字符串、元組或字典,因為整數(shù)需要不同的存儲需求和速度與空間的權(quán)衡。因此,Python 內(nèi)存管理器將一些工作分配給對象特定分配器,但確保后者在私有堆的范圍內(nèi)運行。
Python 堆內(nèi)存的管理是由解釋器來執(zhí)行,用戶對它沒有控制權(quán),即使他們經(jīng)常操作指向堆內(nèi)內(nèi)存塊的對象指針,理解這一點十分重要。Python 對象和其他內(nèi)部緩沖區(qū)的堆空間分配是由 Python 內(nèi)存管理器按需通過本文檔中列出的 Python/C API 函數(shù)進行的。
為了避免內(nèi)存破壞,擴展的作者永遠不應該試圖用 C 庫函數(shù)導出的函數(shù)來對 Python 對象進行操作,這些函數(shù)包括: malloc(), calloc(), realloc() 和 free()。這將導致 C 分配器和 Python 內(nèi)存管理器之間的混用,引發(fā)嚴重后果,這是由于它們實現(xiàn)了不同的算法,并在不同的堆上操作。但是,我們可以安全地使用 C 庫分配器為單獨的目的分配和釋放內(nèi)存塊,如下例所示:
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 緩沖區(qū)的內(nèi)存請求是由 C 庫分配器處理的。Python 內(nèi)存管理器只參與了分配作為結(jié)果返回的字節(jié)對象。
然而,在大多數(shù)情況下,建議專門從 Python 堆中分配內(nèi)存,因為后者由 Python 內(nèi)存管理器控制。例如,當解釋器擴展了用 C 寫的新對象類型時,就必須這樣做。使用 Python 堆的另一個原因是希望*通知* Python 內(nèi)存管理器關(guān)于擴展模塊的內(nèi)存需求。即使所請求的內(nèi)存全部只用于內(nèi)部的、高度特定的目的,將所有的內(nèi)存請求交給 Python 內(nèi)存管理器能讓解釋器對其內(nèi)存占用的整體情況有更準確的了解。因此,在某些情況下,Python 內(nèi)存管理器可能會觸發(fā)或不觸發(fā)適當?shù)牟僮鳎缋厥?、?nèi)存壓縮或其他預防性操作。請注意,通過使用前面例子中所示的 C 庫分配器,為 I/O 緩沖區(qū)分配的內(nèi)存會完全不受 Python 內(nèi)存管理器管理。
參見
環(huán)境變量 PYTHONMALLOC 可被用來配置 Python 所使用的內(nèi)存分配器。
環(huán)境變量 PYTHONMALLOCSTATS 可以用來在每次創(chuàng)建和關(guān)閉新的 pymalloc 對象區(qū)域時打印 pymalloc 內(nèi)存分配器 的統(tǒng)計數(shù)據(jù)。
原始內(nèi)存接口?
以下函數(shù)集封裝了系統(tǒng)分配器。這些函數(shù)是線程安全的,不需要持有 GIL。
default raw memory allocator 使用這些函數(shù):malloc()、 calloc()、 realloc() 和 free();申請零字節(jié)時則調(diào)用 malloc(1)``(或 ``calloc(1, 1))
3.4 新版功能.
-
void*
PyMem_RawMalloc(size_t?n)? 分配 n 個字節(jié),并返回一個指向分配的內(nèi)存的
void*類型指針,如果請求失敗,則返回NULL。請求零字節(jié)可能返回一個獨特的非
NULL指針,就像調(diào)用了``PyMem_RawMalloc(1)`` 一樣。但是內(nèi)存不會以任何方式被初始化。
-
void*
PyMem_RawCalloc(size_t?nelem, size_t?elsize)? 分配 nelem 個元素,每個元素的大小為 elsize 字節(jié),并返回指向分配的內(nèi)存的
void*/類型指針,如果請求失敗,則返回``NULL``。內(nèi)存被初始化為零。請求零字節(jié)可能返回一個獨特的非
NULL指針,就像調(diào)用了``PyMem_RawCalloc(1, 1)`` 一樣。3.5 新版功能.
-
void*
PyMem_RawRealloc(void?*p, size_t?n)? 將 p 指向的內(nèi)存塊大小調(diào)整為 n 字節(jié)。以新舊內(nèi)存塊大小中的最小值為準,其中內(nèi)容保持不變,
如果*p*是``NULL``,則相當于調(diào)用
PyMem_RawMalloc(n);如果 n 等于 0,則內(nèi)存塊大小會被調(diào)整,但不會被釋放,返回非NULL指針。除非 p 是
NULL,否則它必須是之前調(diào)用PyMem_RawMalloc()、PyMem_RawRealloc()或PyMem_RawCalloc()所返回的。如果請求失敗,
PyMem_RawRealloc()返回NULL, p 仍然是指向先前內(nèi)存區(qū)域的有效指針。
-
void
PyMem_RawFree(void?*p)? 釋放 p 指向的內(nèi)存塊。除非 p 是
NULL,否則它必須是之前調(diào)用PyMem_RawMalloc()、PyMem_RawRealloc()或PyMem_RawCalloc()所返回的指針。否則,或在PyMem_RawFree(p)之前已經(jīng)調(diào)用過的情況下,未定義的行為會發(fā)生。如果 p 是
NULL, 那么什么操作也不會進行。
內(nèi)存接口?
以下函數(shù)集,仿照 ANSI C 標準,并指定了請求零字節(jié)時的行為,可用于從Python堆分配和釋放內(nèi)存。
默認內(nèi)存分配器 使用了 pymalloc 內(nèi)存分配器.
警告
在使用這些函數(shù)時,必須持有 全局解釋器鎖(GIL) 。
在 3.6 版更改: 現(xiàn)在默認的分配器是 pymalloc 而非系統(tǒng)的 malloc() 。
-
void*
PyMem_Malloc(size_t?n)? 分配 n 個字節(jié),并返回一個指向分配的內(nèi)存的
void*類型指針,如果請求失敗,則返回NULL。請求零字節(jié)可能返回一個獨特的非
NULL指針,就像調(diào)用了``PyMem_Malloc(1)`` 一樣。但是內(nèi)存不會以任何方式被初始化。
-
void*
PyMem_Calloc(size_t?nelem, size_t?elsize)? 分配 nelem 個元素,每個元素的大小為 elsize 字節(jié),并返回指向分配的內(nèi)存的
void*/類型指針,如果請求失敗,則返回``NULL``。內(nèi)存被初始化為零。請求零字節(jié)可能返回一個獨特的非
NULL指針,就像調(diào)用了``PyMem_Calloc(1, 1)`` 一樣。3.5 新版功能.
-
void*
PyMem_Realloc(void?*p, size_t?n)? 將 p 指向的內(nèi)存塊大小調(diào)整為 n 字節(jié)。以新舊內(nèi)存塊大小中的最小值為準,其中內(nèi)容保持不變,
如果*p*是``NULL``,則相當于調(diào)用
PyMem_Malloc(n);如果 n 等于 0,則內(nèi)存塊大小會被調(diào)整,但不會被釋放,返回非NULL指針。除非 p 是
NULL,否則它必須是之前調(diào)用PyMem_Malloc()、PyMem_Realloc()或PyMem_Calloc()所返回的。如果請求失敗,
PyMem_Realloc()返回NULL, p 仍然是指向先前內(nèi)存區(qū)域的有效指針。
-
void
PyMem_Free(void?*p)? 釋放 p 指向的內(nèi)存塊。除非 p 是
NULL,否則它必須是之前調(diào)用PyMem_Malloc()、PyMem_Realloc()或PyMem_Calloc()所返回的指針。否則,或在PyMem_Free(p)之前已經(jīng)調(diào)用過的情況下,未定義的行為會發(fā)生。如果 p 是
NULL, 那么什么操作也不會進行。
以下面向類型的宏為方便而提供。 注意 TYPE 可以指任何 C 類型。
-
TYPE*
PyMem_New(TYPE, size_t?n)? 與
PyMem_Malloc()相同,但分配(n * sizeof(TYPE))字節(jié)的內(nèi)存。返回一個轉(zhuǎn)換為TYPE*的指針。內(nèi)存不會以任何方式被初始化。
-
TYPE*
PyMem_Resize(void?*p, TYPE, size_t?n)? 與
PyMem_Realloc()相同,但內(nèi)存塊的大小被調(diào)整為(n * sizeof(TYPE))字節(jié)。返回一個轉(zhuǎn)換為TYPE|*類型的指針。返回時, p 是指向新內(nèi)存區(qū)域的指針;如果失敗,則返回NULL。這是一個 C 預處理宏, p 總是被重新賦值。請保存 p 的原始值,以避免在處理錯誤時丟失內(nèi)存。
-
void
PyMem_Del(void?*p)? 與
PyMem_Free()相同
此外,我們還提供了以下宏集用于直接調(diào)用 Python 內(nèi)存分配器,而不涉及上面列出的 C API 函數(shù)。但是請注意,使用它們并不能保證跨 Python 版本的二進制兼容性,因此在擴展模塊被棄用。
PyMem_MALLOC(size)PyMem_NEW(type, size)PyMem_REALLOC(ptr, size)PyMem_RESIZE(ptr, type, size)PyMem_FREE(ptr)PyMem_DEL(ptr)
對象分配器?
以下函數(shù)集,仿照 ANSI C 標準,并指定了請求零字節(jié)時的行為,可用于從Python堆分配和釋放內(nèi)存。
默認對象分配器 使用 pymalloc 內(nèi)存分配器.
警告
在使用這些函數(shù)時,必須持有 全局解釋器鎖(GIL) 。
-
void*
PyObject_Malloc(size_t?n)? 分配 n 個字節(jié),并返回一個指向分配的內(nèi)存的
void*類型指針,如果請求失敗,則返回NULL。請求零字節(jié)可能返回一個獨特的非
NULL指針,就像調(diào)用了``PyObject_Malloc(1)`` 一樣。但是內(nèi)存不會以任何方式被初始化。
-
void*
PyObject_Calloc(size_t?nelem, size_t?elsize)? 分配 nelem 個元素,每個元素的大小為 elsize 字節(jié),并返回指向分配的內(nèi)存的
void*/類型指針,如果請求失敗,則返回``NULL``。內(nèi)存被初始化為零。請求零字節(jié)可能返回一個獨特的非
NULL指針,就像調(diào)用了``PyObject_Calloc(1, 1)`` 一樣。3.5 新版功能.
-
void*
PyObject_Realloc(void?*p, size_t?n)? 將 p 指向的內(nèi)存塊大小調(diào)整為 n 字節(jié)。以新舊內(nèi)存塊大小中的最小值為準,其中內(nèi)容保持不變,
如果*p*是``NULL``,則相當于調(diào)用
PyObject_Malloc(n);如果 n 等于 0,則內(nèi)存塊大小會被調(diào)整,但不會被釋放,返回非NULL指針。除非 p 是
NULL,否則它必須是之前調(diào)用PyObject_Malloc()、PyObject_Realloc()或PyObject_Calloc()所返回的。如果請求失敗,
PyObject_Realloc()返回NULL, p 仍然是指向先前內(nèi)存區(qū)域的有效指針。
-
void
PyObject_Free(void?*p)? 釋放 p 指向的內(nèi)存塊。除非 p 是
NULL,否則它必須是之前調(diào)用PyObject_Malloc()、PyObject_Realloc()或PyObject_Calloc()所返回的指針。否則,或在PyObject_Free(p)之前已經(jīng)調(diào)用過的情況下,未定義的行為會發(fā)生。如果 p 是
NULL, 那么什么操作也不會進行。
默認內(nèi)存分配器?
默認內(nèi)存分配器:
配置 |
名稱 |
PyMem_RawMalloc |
PyMem_Malloc |
PyObject_Malloc |
|---|---|---|---|---|
發(fā)布版本 |
|
|
|
|
調(diào)試構(gòu)建 |
|
|
|
|
沒有 pymalloc 的發(fā)布版本 |
|
|
|
|
沒有 pymalloc 的調(diào)試構(gòu)建 |
|
|
|
|
說明:
名稱: 環(huán)境變量
PYTHONMALLOC的值malloc: 來自 C 標準庫的系統(tǒng)分配, C 函數(shù)malloc(),calloc(),realloc()andfree()pymalloc: pymalloc 內(nèi)存分配器"+ debug": 帶有
PyMem_SetupDebugHooks()安裝的調(diào)試鉤子
自定義內(nèi)存分配器?
3.4 新版功能.
-
PyMemAllocatorEx? 用于描述內(nèi)存塊分配器的結(jié)構(gòu)體。包含四個字段:
域
含義
void *ctx作為第一個參數(shù)傳入的用戶上下文
void* malloc(void *ctx, size_t size)分配一個內(nèi)存塊
void* calloc(void *ctx, size_t nelem, size_t elsize)分配一個初始化為 0 的內(nèi)存塊
void* realloc(void *ctx, void *ptr, size_t new_size)分配一個內(nèi)存塊或調(diào)整其大小
void free(void *ctx, void *ptr)釋放一個內(nèi)存塊
在 3.5 版更改: The
PyMemAllocatorstructure was renamed toPyMemAllocatorExand a newcallocfield was added.
-
PyMemAllocatorDomain? 用來識別分配器域的枚舉類。域有:
-
PYMEM_DOMAIN_RAW? 函數(shù)
-
PYMEM_DOMAIN_MEM? 函數(shù)
-
PYMEM_DOMAIN_OBJ? 函數(shù)
-
-
void
PyMem_GetAllocator(PyMemAllocatorDomain?domain, PyMemAllocatorEx?*allocator)? 獲取指定域的內(nèi)存塊分配器。
-
void
PyMem_SetAllocator(PyMemAllocatorDomain?domain, PyMemAllocatorEx?*allocator)? 設(shè)置指定域的內(nèi)存塊分配器。
當請求零字節(jié)時,新的分配器必須返回一個獨特的非
NULL指針。對于
PYMEM_DOMAIN_RAW域,分配器必須是線程安全的:當分配器被調(diào)用時,不持有 全局解釋器鎖 。如果新的分配器不是鉤子(不調(diào)用之前的分配器),必須調(diào)用
PyMem_SetupDebugHooks()函數(shù)在新分配器上重新安裝調(diào)試鉤子。
-
void
PyMem_SetupDebugHooks(void)? 設(shè)置檢測 Python 內(nèi)存分配器函數(shù)中錯誤的鉤子。
新分配的內(nèi)存由字節(jié)
0xCD(CLEANBYTE) 填充,釋放的內(nèi)存由字節(jié)0xDD(DEADBYTE)填充。內(nèi)存塊被 "禁止字節(jié)" 包圍(FORBIDDENBYTE:字節(jié)0xFD)。運行時檢查:
檢測對 API 的違反,例如:對用
PyMem_Malloc()分配的緩沖區(qū)調(diào)用PyObject_Free()。檢測緩沖區(qū)起始位置前的寫入(緩沖區(qū)下溢)。
檢測緩沖區(qū)終止位置后的寫入(緩沖區(qū)溢出)。
檢測當調(diào)用
PYMEM_DOMAIN_OBJ(如:PyObject_Malloc()) 和PYMEM_DOMAIN_MEM(如:PyMem_Malloc()) 域的分配器函數(shù)時 GIL 已被保持。
在出錯時,調(diào)試鉤子使用
tracemalloc模塊來回溯內(nèi)存塊被分配的位置。只有當tracemalloc正在追蹤 Python 內(nèi)存分配,并且內(nèi)存塊被追蹤時,才會顯示回溯。如果 Python 是在調(diào)試模式下編譯的,這些鉤子是 installed by default 。環(huán)境變量
PYTHONMALLOC可以用來在發(fā)布模式編譯的 Python 上安裝調(diào)試鉤子。在 3.6 版更改: 這個函數(shù)現(xiàn)在也適用于以 發(fā)布模式編譯的 Python。在出錯時,調(diào)試鉤子現(xiàn)在使用
tracemalloc來回溯內(nèi)存塊被分配的位置。調(diào)試鉤子現(xiàn)在也檢查當PYMEM_DOMAIN_OBJ和PYMEM_DOMAIN_MEM域的函數(shù)被調(diào)用時,全局解釋器鎖是否被持有。在 3.7.3 版更改: 字節(jié)模式
0xCB(CLEANBYTE)、0xDB(DEADBYTE) 和0xFB(FORBIDDENBYTE) 已被0xCD、0xDD和0xFD替代以使用與 Windows CRT 調(diào)試malloc()和free()相同的值。
pymalloc 分配器?
Python 有為具有短生命周期的小對象(小于或等于 512 字節(jié))優(yōu)化的 pymalloc 分配器。它使用固定大小為 256 KiB 的稱為 "arenas" 的內(nèi)存映射。對于大于512字節(jié)的分配,它回到使用 PyMem_RawMalloc() 和 PyMem_RawRealloc() 。
pymalloc 是 PYMEM_DOMAIN_MEM (例如: PyMem_Malloc()) 和 PYMEM_DOMAIN_OBJ (例如: PyObject_Malloc()) 域的 默認分配器 。
arena 分配器使用以下函數(shù):
Windows 上的
VirtualAlloc()andVirtualFree(),mmap()和munmap(),如果可用,否則,
malloc()和free()。
自定義 pymalloc Arena 分配器?
3.4 新版功能.
-
PyObjectArenaAllocator? 用來描述一個 arena 分配器的結(jié)構(gòu)體。這個結(jié)構(gòu)體有三個字段:
域
含義
void *ctx作為第一個參數(shù)傳入的用戶上下文
void* alloc(void *ctx, size_t size)分配一塊 size 字節(jié)的區(qū)域
void free(void *ctx, size_t size, void *ptr)釋放一塊區(qū)域
-
PyObject_GetArenaAllocator(PyObjectArenaAllocator?*allocator)? 獲取 arena 分配器
-
PyObject_SetArenaAllocator(PyObjectArenaAllocator?*allocator)? 設(shè)置 arena 分配器
tracemalloc C API?
3.7 新版功能.
-
int
PyTraceMalloc_Track(unsigned int?domain, uintptr_t?ptr, size_t?size)? 在
tracemalloc模塊中跟蹤一個已分配的內(nèi)存塊。成功時返回
0,出錯時返回-1(無法分配內(nèi)存來保存跟蹤信息)。 如果禁用了 tracemalloc 則返回-2。如果內(nèi)存塊已被跟蹤,則更新現(xiàn)有跟蹤信息。
-
int
PyTraceMalloc_Untrack(unsigned int?domain, uintptr_t?ptr)? 在
tracemalloc模塊中取消跟蹤一個已分配的內(nèi)存塊。 如果內(nèi)存塊未被跟蹤則不執(zhí)行任何操作。如果 tracemalloc 被禁用則返回
-2,否則返回0。
例子?
以下是來自 概述 小節(jié)的示例,經(jīng)過重寫以使 I/O 緩沖區(qū)是通過使用第一個函數(shù)集從 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;
使用面向類型函數(shù)集的相同代碼:
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;
請注意在以上兩個示例中,緩沖區(qū)總是通過歸屬于相同集的函數(shù)來操縱的。 事實上,對于一個給定的內(nèi)存塊必須使用相同的內(nèi)存 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 堆的原始內(nèi)存塊的函數(shù)之外, Python 中的對象是通過 PyObject_New(), PyObject_NewVar() 和 PyObject_Del() 來分配和釋放的。
這些將在有關(guān)如何在 C 中定義和實現(xiàn)新對象類型的下一章中講解。
