메모리 관리¶
개요¶
파이썬의 메모리 관리에는 모든 파이썬 객체와 데이터 구조를 포함하는 비공개 힙(private heap)을 수반합니다. 이 비공개 힙의 관리는 파이썬 메모리 관리자에 의해 내부적으로 이루어집니다. 파이썬 메모리 관리자는 공유, 세그먼트 화, 사전 할당 또는 캐싱과 같은 동적 스토리지 관리의 다양한 측면을 처리하는 서로 다른 구성 요소를 가지고 있습니다.
가장 낮은 수준에서, 원시 메모리 할당자는 운영 체제의 메모리 관리자와 상호 작용하여 비공개 힙에 모든 파이썬 관련 데이터를 저장하기에 충분한 공간이 있는지 확인합니다. 원시 메모리 할당자 위에, 여러 개의 객체별 할당자가 같은 힙에서 작동하며 각 객체 형의 특성에 맞는 고유한 메모리 관리 정책을 구현합니다. 예를 들어, 정수는 다른 스토리지 요구 사항과 속도/공간 절충을 의미하므로, 정수 객체는 힙 내에서 문자열, 튜플 또는 딕셔너리와는 다르게 관리됩니다. 따라서 파이썬 메모리 관리자는 일부 작업을 객체별 할당자에게 위임하지만, 후자가 비공개 힙의 경계 내에서 작동하도록 합니다.
파이썬 힙의 관리는 인터프리터 자체에 의해 수행되며, 사용자는 힙 내부의 메모리 블록에 대한 객체 포인터를 규칙적으로 조작하더라도, 사용자가 제어할 수 없다는 것을 이해하는 것이 중요합니다. 파이썬 객체와 기타 내부 버퍼를 위한 힙 공간 할당은 이 설명서에 나열된 파이썬/C API 함수를 통해 파이썬 메모리 관리자의 요청에 따라 수행됩니다.
메모리 손상을 피하고자, 확장 작성자는 C 라이브러리에서 내보낸 함수를 파이썬 객체에 대해 실행하지 않아야 합니다: malloc(), calloc(), realloc() 및 free(). 그렇게 한다면, 서로 다른 알고리즘을 구현하고 다른 힘에 작동하기 때문에, C 할당자와 파이썬 메모리 관리자 간에 혼합 호출이 발생하여 치명적인 결과를 초래합니다. 그러나, 다음 예제와 같이 개별 목적으로 C 라이브러리 할당자를 사용하여 메모리 블록을 안전하게 할당하고 해제할 수 있습니다:
PyObject *res;
char *buf = (char *) malloc(BUFSIZ); /* I/O 용 */
if (buf == NULL)
return PyErr_NoMemory();
...buf 를 사용하는 I/O 연산을 수행합니다...
res = PyBytes_FromString(buf);
free(buf); /* malloc 으로 할당했습니다 */
return res;
이 예에서, I/O 버퍼에 대한 메모리 요청은 C 라이브러리 할당자에 의해 처리됩니다. 파이썬 메모리 관리자는 결과로 반환되는 바이트열 객체의 할당에만 관여합니다.
그러나 대부분의 상황에서는, 후자가 Python 메모리 관리자의 제어 하에 있기 때문에, Python 힙에서 메모리를 할당하는 것을 권장합니다. 예를 들어, 인터프리터가 C로 작성된 새로운 객체 타입으로 확장될 때 이것이 필요합니다. Python 힙을 사용하는 또 다른 이유는 확장 모듈의 메모리 요구사항에 대해 Python 메모리 관리자에게 알려주고자 하는 바람 때문입니다. 요청된 메모리가 내부적이고 매우 특정 목적만을 위해서 사용될 때라도, 모든 메모리 요청을 Python 메모리 관리자에 위임하면 인터프리터가 전체적인 메모리 사용량에 대한 더 정확한 이미지를 갖게 됩니다. 결과적으로, 특정 상황에서 Python 메모리 관리자는 가비지 컬렉션, 메모리 압축 또는 기타 예방 절차와 같은 적절한 작업을 발생시킬 수도 있고 그렇지 않을 수도 있습니다. 이전 예시에서 보여준 C 라이브러리 할당자를 사용함으로써 I/O 버퍼에 할당된 메모리가 Python 메모리 관리자를 완전히 벗어난다는 점에 유의하십시오.
더 보기
PYTHONMALLOC 환경 변수를 사용하여 파이썬에서 사용하는 메모리 할당자를 구성할 수 있습니다.
PYTHONMALLOCSTATS 환경 변수는 새로운 pymalloc 객체 아레나(arena)가 만들어질 때마다 그리고 종료 시 pymalloc 메모리 할당자의 통계를 인쇄하는 데 사용될 수 있습니다.
할당자 도메인¶
모든 할당 함수는 세 가지 다른 “도메인” 중 하나에 속합니다 (또한 :c:type:`PyMemAllocatorDomain`도 참조하십시오). 이 도메인들은 서로 다른 할당 전략을 나타내며 다양한 목적에 맞게 최적화되어 있습니다. 모든 도메인이 메모리를 어떻게 할당하는지 또는 각 도메인이 어떤 내부 함수를 호출하는지에 대한 구체적인 세부 사항은 구현 세부 사항으로 간주되지만, 디버깅 목적으로는 :ref:`default-memory-allocators`에서 단순화된 표를 찾을 수 있습니다. 메모리 블록을 할당하고 해제하는 데 사용되는 API는 반드시 동일한 도메인 출처여야 합니다. 예를 들어, :c:func:`PyMem_Malloc`을 사용하여 할당된 메모리를 해제하려면 :c:func:`PyMem_Free`를 사용해야 합니다.
세 가지 할당 도메인은 다음과 같습니다:
Raw 도메인: 할당이 시스템 할당자에게 반드시 가야 하거나, 할당자가 붙어 있는 스레드 상태 없이 작동할 수 있는 범용 메모리 버퍼용 메모리 할당을 목적으로 합니다. 메모리는 시스템으로부터 직접 요청됩니다. :ref:`원시 메모리 인터페이스 <raw-memoryinterface>`를 참조하십시오.
“Mem” 도메인: 할당이 :term:`붙어 있는 스레드 상태 <attached thread state>`를 사용하여 수행되어야 하는 Python 버퍼 및 범용 메모리 버퍼용 메모리 할당을 목적으로 합니다. 메모리는 Python 비공개 힙에서 가져옵니다. :ref:`메모리 인터페이스 <memoryinterface>`를 참조하십시오.
객체 도메인: Python 객체용 메모리 할당을 목적으로 합니다. 메모리는 Python 비공개 힙에서 가져옵니다. :ref:`객체 할당자 <objectinterface>`를 참조하십시오.
참고
free-threaded 빌드는 Python 객체만 “object” 도메인을 사용하여 할당해야 하며 모든 Python 객체가 해당 도메인을 사용하여 할당되어야 함을 요구합니다. 이는 이전 Python 버전과 다르며, 이전에는 단순한 모범 사례일 뿐 하드웨어 요구 사항은 아니었습니다.
예를 들어, 버퍼(Python 객체가 아닌 것)는 PyMem_Malloc(), PyMem_RawMalloc(), 또는 :c:func:`malloc`을 사용하여 할당해야 하며, :c:func:`PyObject_Malloc`은 안 됩니다.
:ref:`메모리 할당 API <free-threaded-memory-allocation>`를 참조하십시오.
원시 메모리 인터페이스¶
다음 함수 세트는 시스템 할당자의 래퍼입니다. 이 함수들은 스레드 안전하므로, 스레드 상태 필요하지 않습니다.
기본 원시 메모리 할당자는 다음 함수를 사용합니다: malloc(), calloc(), realloc() 및 free(); 0바이트를 요청할 때 malloc(1)(또는 calloc(1, 1))을 호출합니다.
Added in version 3.4.
-
void *PyMem_RawMalloc(size_t n)¶
- 상의 안정 ABI 버전 3.13 이후로.
n 바이트를 할당하고 할당된 메모리를 가리키는 void* 형의 포인터를 반환하거나, 요청이 실패하면
NULL을 반환합니다.0바이트를 요청하면 가능하면
PyMem_RawMalloc(1)이 대신 호출된 것처럼 가능하면 고유한NULL이 아닌 포인터를 반환합니다. 메모리는 어떤 식으로든 초기화되지 않습니다.
-
void *PyMem_RawCalloc(size_t nelem, size_t elsize)¶
- 상의 안정 ABI 버전 3.13 이후로.
각각 크기가 elsize 바이트인 nelem 개의 요소를 할당하고, 할당된 메모리를 가리키는 void* 형식의 포인터를 반환하거나, 요청이 실패하면
NULL을 반환합니다. 메모리는 0으로 초기화됩니다.0개의 요소나 0바이트 크기의 요소를 요청하면
PyMem_RawCalloc(1, 1)이 대신 호출된 것처럼 가능하면 고유한NULL이 아닌 포인터를 반환합니다.Added in version 3.5.
-
void *PyMem_RawRealloc(void *p, size_t n)¶
- 상의 안정 ABI 버전 3.13 이후로.
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)¶
- 상의 안정 ABI 버전 3.13 이후로.
p가 가리키는 메모리 블록을 해제합니다. p는
PyMem_RawMalloc(),PyMem_RawRealloc()또는PyMem_RawCalloc()에 대한 이전 호출로 반환된 것이어야 합니다. 그렇지 않거나PyMem_RawFree(p)가 앞서 호출되었으면, 정의되지 않은 동작이 일어납니다.p가
NULL이면, 아무 작업도 수행되지 않습니다.
메모리 인터페이스¶
ANSI C 표준에 따라 모델링 되었지만 0바이트를 요청할 때의 동작을 지정한 다음 함수 집합은 파이썬 힙에서 메모리를 할당하고 해제하는 데 사용할 수 있습니다.
GIL이 활성화된 빌드(기본 빌드)에서는 :ref:`default memory allocator <default-memory-allocators>`가 :ref:`pymalloc memory allocator <pymalloc>`를 사용하며, :term:`free-threaded build`에서는 기본값이 :ref:`mimalloc memory allocator <mimalloc>`입니다.
경고
이러한 함수를 사용할 때는 :term:`attached thread state`가 존재해야 합니다.
버전 3.6에서 변경: 기본 할당자는 이제 시스템 malloc() 대신 pymalloc 입니다.
버전 3.13에서 변경: free-threaded 빌드에서는 기본 할당자가 이제 :ref:`mimalloc <mimalloc>`입니다.
-
void *PyMem_Malloc(size_t n)¶
- 상의 안정 ABI.
n 바이트를 할당하고 할당된 메모리를 가리키는 void* 형의 포인터를 반환하거나, 요청이 실패하면
NULL을 반환합니다.0바이트를 요청하면
PyMem_Malloc(1)이 대신 호출된 것처럼 가능하면 고유한NULL이 아닌 포인터를 반환합니다. 메모리는 어떤 식으로든 초기화되지 않습니다.
-
void *PyMem_Calloc(size_t nelem, size_t elsize)¶
- 상의 안정 ABI 버전 3.7 이후로.
각각 크기가 elsize 바이트인 nelem 개의 요소를 할당하고, 할당된 메모리를 가리키는 void* 형식의 포인터를 반환하거나, 요청이 실패하면
NULL을 반환합니다. 메모리는 0으로 초기화됩니다.0개의 요소나 0바이트 크기의 요소를 요청하면
PyMem_Calloc(1, 1)이 대신 호출된 것처럼 가능하면 고유한NULL이 아닌 포인터를 반환합니다.Added in version 3.5.
-
void *PyMem_Realloc(void *p, size_t n)¶
- 상의 안정 ABI.
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)¶
- 상의 안정 ABI.
p가 가리키는 메모리 블록을 해제합니다. p는
PyMem_Malloc(),PyMem_Realloc()또는PyMem_Calloc()에 대한 이전 호출이 반환한 것이어야 합니다. 그렇지 않거나PyMem_Free(p)가 앞서 호출되었으면 정의되지 않은 동작이 일어납니다.p가
NULL이면, 아무 작업도 수행되지 않습니다.
편의를 위해 다음과 같은 형 지향 매크로가 제공됩니다. TYPE이 모든 C형을 나타냄에 유의하십시오.
-
PyMem_New(TYPE, n)¶
PyMem_Malloc()과 같지만,(n * sizeof(TYPE))바이트의 메모리를 할당합니다.TYPE*로 캐스트 된 포인터를 반환합니다. 메모리는 어떤 식으로든 초기화되지 않습니다.
-
PyMem_Resize(p, TYPE, n)¶
PyMem_Realloc()과 같지만, 메모리 블록의 크기는(n * sizeof(TYPE))바이트로 조정됩니다.TYPE*로 캐스트 된 포인터를 반환합니다. 반환한 후에, p는 새로운 메모리 영역에 대한 포인터이거나, 실패하면NULL이 됩니다.이것은 C 전처리기 매크로입니다; p는 항상 다시 대입됩니다. 에러를 처리할 때 메모리 손실을 피하려면 p의 원래 값을 보관하십시오.
-
void PyMem_Del(void *p)¶
PyMem_Free()와 같습니다.
폐지된 에일리어스¶
이들은 기존 함수 및 매크로에 대한 soft deprecated 에일리어스입니다. 이는 순수하게 하위 호환성을 위해 존재합니다.
폐지된 에일리어스 |
해당 함수 또는 매크로 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
버전 3.4에서 변경: 매크로는 이제 해당 함수 및 매크로의 에일리어스입니다. 이전과 마찬가지로 동작 방식은 동일했지만, 사용 시 파이썬 버전을 가로지르는 바이너리 호환성이 필연적으로 보장되지는 않았습니다.
버전 2.0부터 폐지됨.
객체 할당자¶
ANSI C 표준에 따라 모델링 되었지만 0바이트를 요청할 때의 동작을 지정한 다음 함수 집합은 파이썬 힙에서 메모리를 할당하고 해제하는 데 사용할 수 있습니다.
참고
이 도메인에서 할당 함수를 가로챌 때, 이 할당자들이 반환하는 메모리가 Customize Memory Allocators 섹션에서 설명하는 방법을 통해 파이썬 객체로 성공적으로 형 변환될 것이라는 보장은 없습니다.
default object allocator 빌드에서는 대신 기본값이 :ref:`mimalloc memory allocator <mimalloc>`입니다.
경고
이러한 함수를 사용할 때는 :term:`attached thread state`가 존재해야 합니다.
-
void *PyObject_Malloc(size_t n)¶
- 상의 안정 ABI.
n 바이트를 할당하고 할당된 메모리를 가리키는 void* 형의 포인터를 반환하거나, 요청이 실패하면
NULL을 반환합니다.0바이트를 요청하면
PyObject_Malloc(1)이 대신 호출된 것처럼 가능하면 고유한NULL이 아닌 포인터를 반환합니다. 메모리는 어떤 식으로든 초기화되지 않습니다.
-
void *PyObject_Calloc(size_t nelem, size_t elsize)¶
- 상의 안정 ABI 버전 3.7 이후로.
각각 크기가 elsize 바이트인 nelem 개의 요소를 할당하고, 할당된 메모리를 가리키는 void* 형식의 포인터를 반환하거나, 요청이 실패하면
NULL을 반환합니다. 메모리는 0으로 초기화됩니다.0개의 요소나 0바이트 크기의 요소를 요청하면
PyObject_Calloc(1, 1)이 대신 호출된 것처럼 가능하면 고유한NULL이 아닌 포인터를 반환합니다.Added in version 3.5.
-
void *PyObject_Realloc(void *p, size_t n)¶
- 상의 안정 ABI.
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)¶
- 상의 안정 ABI.
p가 가리키는 메모리 블록을 해제합니다. 이 블록은
PyObject_Malloc(),PyObject_Realloc()또는PyObject_Calloc()에 대한 이전 호출에 의해 반환된 것이어야 합니다. 그렇지 않거나PyObject_Free(p)가 이전에 호출되었으면 정의되지 않은 동작이 일어납니다.p가
NULL이면, 아무 작업도 수행되지 않습니다.객체의 메모리를 해제하기 위해 이것을 직접 호출하지 말고, 대신 타입의
tp_free슬롯을 호출하십시오.PyObject_GC_New또는 :c:macro:`PyObject_GC_NewVar`에 의해 할당된 메모리에 대해서는 이것을 사용하지 말고, 대신 :c:func:`PyObject_GC_Del`을 사용하십시오.더 보기
:c:func:`PyObject_GC_Del`은 가비지 컬렉션을 지원하는 타입에 의해 할당된 메모리에 대해 이 함수와 동일합니다.
기본 메모리 할당자¶
기본 메모리 할당자:
구성 |
이름 |
PyMem_RawMalloc |
PyMem_Malloc |
PyObject_Malloc |
|---|---|---|---|---|
릴리스 빌드 |
|
|
|
|
디버그 빌드 |
|
|
|
|
pymalloc 없는 배포 빌드 |
|
|
|
|
pymalloc 없는 디버그 빌드 |
|
|
|
|
자유 스레드 빌드 |
|
|
|
|
자유 스레드 디버그 빌드 |
|
|
|
|
범례:
이름:
PYTHONMALLOC환경 변수의 값.malloc: 표준 C 라이브러리의 시스템 할당자, C 함수:malloc(),calloc(),realloc()및free().pymalloc: pymalloc 메모리 할당자.mimalloc: mimalloc memory allocator.“+ 디버그”: 파이썬 메모리 할당기의 디버그 훅 포함.
“디버그 빌드”: debug mode로 빌드된 Python.
메모리 할당자 사용자 정의¶
Added in version 3.4.
-
type 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에서 변경:
PyMemAllocator구조체의 이름이PyMemAllocatorEx로 바뀌고 새로운calloc필드가 추가되었습니다.
-
type PyMemAllocatorDomain¶
할당자 도메인을 식별하는 데 사용되는 열거형. 도메인:
-
PYMEM_DOMAIN_RAW¶
함수:
-
PYMEM_DOMAIN_MEM¶
함수:
-
PYMEM_DOMAIN_OBJ¶
함수:
-
PYMEM_DOMAIN_RAW¶
-
void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)¶
지정된 도메인의 메모리 블록 할당자를 가져옵니다.
-
void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)¶
지정된 도메인의 메모리 블록 할당자를 설정합니다.
새 할당자는 0바이트를 요청할 때 고유한
NULL이 아닌 포인터를 반환해야 합니다.PYMEM_DOMAIN_RAW도메인의 경우, 할당자는 스레드 안전해야 합니다. 할당자가 호출될 때 :term:`thread state`는 :term:`attached <attached thread state>`가 아닙니다.나머지 도메인의 경우에도 할당자는 스레드 안전해야 합니다. 할당자는 :term:`GIL`을 공유하지 않는 여러 인터프리터에서 호출될 수 있습니다.
새 할당자가 훅이 아니면 (이전 할당자를 호출하지 않으면),
PyMem_SetupDebugHooks()함수를 호출하여 새 할당자 위에 디버그 훅을 다시 설치해야 합니다.또한
PyPreConfig.allocator및 :ref:`PyPreConfig으로 Python 사전 초기화 <c-preinit>`도 참조하십시오.경고
:c:func:`PyMem_SetAllocator`는 다음과 같은 계약을 가지고 있습니다:
사용자 정의 메모리 할당자를 설치하기 위해
Py_PreInitialize()이후에Py_InitializeFromConfig()이전에 호출할 수 있습니다. 도메인이 부과하는 제약 사항 외에는 설치된 할당자에 대한 제한이 없습니다 (예를 들어, Raw Domain은 attached thread state 없이 할당자가 호출되도록 허용합니다). 자세한 내용은 allocator domains 섹션을 참조하십시오.Python이 초기화를 마친 후(즉, :c:func:`Py_InitializeFromConfig`가 호출된 후) 호출되는 경우, 할당자는 기존 할당자를 반드시 래핑해야 합니다. 현재 할당자를 임의의 다른 할당자로 대체하는 것은 지원되지 않습니다.
버전 3.12에서 변경: 모든 할당자는 스레드 안전해야 합니다.
파이썬 메모리 할당자의 디버그 훅¶
Python이 디버그 모드로 빌드될 때, PyMem_SetupDebugHooks() 함수가 메모리 오류를 감지하기 위해 Python 메모리 할당자에 디버그 훅을 설정하기 위해 :ref:`Python preinitialization <c-preinit>`에서 호출됩니다.
PYTHONMALLOC 환경 변수를 사용하여 릴리스 모드에서 컴파일된 파이썬에 디버그 훅을 설치할 수 있습니다 (예: PYTHONMALLOC=debug).
PyMem_SetupDebugHooks() 함수는 :c:func:`PyMem_SetAllocator`를 호출한 후에 디버그 훅을 설정하는 데 사용될 수 있습니다.
이 디버그 훅은 동적으로 할당된 메모리 블록을 특수하고, 인식 가능한 비트 패턴으로 채웁니다. 새로 할당된 메모리는 바이트 0xCD(PYMEM_CLEANBYTE)로 채워지고, 해제된 메모리는 바이트 0xDD(PYMEM_DEADBYTE)로 채워집니다. 메모리 블록은 바이트 0xFD (PYMEM_FORBIDDENBYTE)로 채워진 “금지된 바이트”로 둘러싸입니다. 이 바이트들의 문자열은 유효한 주소, 부동 소수점 또는 ASCII 문자열일 가능성이 낮습니다.
실행 시간 검사:
API 위반 탐지. 예를 들어,
PyMem_Malloc()이 할당한 메모리 블록에PyObject_Free()가 호출되었는지 탐지합니다.버퍼 시작 전에 쓰기 감지 (버퍼 언더플로).
버퍼 끝 뒤에 쓰기 감지 (버퍼 오버플로).
PYMEM_DOMAIN_OBJ(예:PyObject_Malloc()) 및PYMEM_DOMAIN_MEM(예:PyMem_Malloc()) 도메인의 할당자 함수가 호출될 때 :term:`attached thread state`가 존재하는지 확인하십시오.
에러가 발생하면, 디버그 훅은 tracemalloc 모듈을 사용하여 메모리 블록이 할당된 곳의 트레이스백을 가져옵니다. tracemalloc이 파이썬 메모리 할당을 추적 중이고 메모리 블록이 추적될 때만 트레이스백이 표시됩니다.
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]원래 요청된 바이트 수입니다. 이는 size_t 형태이며, 빅 엔디안 방식입니다 (메모리 덤프에서 읽기 더 쉽습니다).
p[-S]API 식별자 (ASCII 문자):
PYMEM_DOMAIN_RAW의 경우'r'입니다.PYMEM_DOMAIN_MEM의 경우'm'입니다.PYMEM_DOMAIN_OBJ의 경우'o'입니다.
p[-S+1:0]PYMEM_FORBIDDENBYTE의 복사본입니다. 언더-쓰기 및 읽기를 감지하는 데 사용됩니다.
p[0:N]요청된 메모리이며, 초기화되지 않은 메모리에 대한 참조를 감지하기 위해 PYMEM_CLEANBYTE 복사본으로 채워집니다. realloc과 같은 함수가 더 큰 메모리 블록을 요청할 때, 초과된 새 바이트도 PYMEM_CLEANBYTE로 채워집니다. free와 같은 함수가 호출될 때는, 해제된 메모리에 대한 참조를 포착하기 위해 이들이 PYMEM_DEADBYTE로 덮어쓰여집니다. realloc과 같은 함수가 더 작은 메모리 블록을 요청할 때는, 초과된 이전 바이트도 PYMEM_DEADBYTE로 채워집니다.
p[N:N+S]PYMEM_FORBIDDENBYTE의 복사본입니다. 오버-쓰기 및 읽기를 감지하는 데 사용됩니다.
p[N+S:N+2*S]PYMEM_DEBUG_SERIALNO매크로가 정의된 경우에만 사용됩니다 (기본적으로 정의되지 않음).malloc과 같은 또는 realloc과 같은 함수 호출 시마다 1이 증가하는 일련번호입니다. 빅 엔디안 :c:type:`size_t`입니다. 나중에 “bad memory”가 감지되면, 이 일련번호는 다음에 실행할 때 중단점을 설정하여 이 블록이 전달된 순간을 포착하는 데 매우 좋은 방법을 제공합니다. obmalloc.c의 static 함수 bumpserialno()만이 일련번호가 증가하는 유일한 곳이며, 따라서 이와 같은 중단점을 쉽게 설정할 수 있습니다.
realloc과 같은 또는 free와 같은 함수는 먼저 각 끝단의 PYMEM_FORBIDDENBYTE 바이트가 손상되지 않았는지 확인합니다. 만약 변경되었다면, 진단 출력이 stderr로 기록되고 Py_FatalError()를 통해 프로그램이 중단됩니다. 다른 주요 실패 모드는 프로그램이 특별한 비트 패턴 중 하나를 읽어 이를 주소로 사용하려고 시도하는 경우에 메모리 오류를 유발하는 것입니다. 디버거로 가서 객체를 확인하면, 전체가 PYMEM_DEADBYTE로 채워지거나 (해제된 메모리 사용을 의미함) PYMEM_CLEANBYTE로 채워졌을 가능성이 높습니다 (초기화되지 않은 메모리 사용을 의미함).
버전 3.6에서 변경: PyMem_SetupDebugHooks() 함수는 이제 릴리스 모드로 컴파일된 Python에서도 작동합니다. 오류 발생 시, 디버그 훅은 메모리 블록이 할당된 트레이스백을 가져오기 위해 tracemalloc`을 사용합니다. 디버그 훅은 또한 :c:macro:`PYMEM_DOMAIN_OBJ 및 PYMEM_DOMAIN_MEM 도메인의 함수가 호출될 때 :term:`attached thread state`가 있는지 확인합니다.
버전 3.8에서 변경: 바이트 패턴 0xCB(PYMEM_CLEANBYTE), 0xDB(PYMEM_DEADBYTE) 및 0xFB(PYMEM_FORBIDDENBYTE)는 윈도우 CRT 디버그 malloc() 및 free()와 같은 값을 사용하도록 0xCD, 0xDD 및 0xFD로 대체되었습니다.
pymalloc 할당자¶
Python에는 수명이 짧은 작은 (512바이트 이하) 객체에 최적화된 pymalloc 할당자가 있습니다. 32비트 플랫폼에서는 256 KiB, 64비트 플랫폼에서는 1 MiB의 고정 크기를 갖는 “아레나(arena)”라는 메모리 매핑을 사용합니다. Python이 --with-pymalloc-hugepages`로 구성되면, 64비트 플랫폼의 아레나 크기는 큰 페이지 크기에 맞게 2 MiB로 증가하며, 아레나 할당은 자동 폴백을 통해 큰 페이지(Linux의 ``MAP_HUGETLB`, Windows의 MEM_LARGE_PAGES)를 사용하려고 시도합니다. 512 바이트보다 큰 할당의 경우 PyMem_RawMalloc() 및 :c:func:`PyMem_RawRealloc`으로 대체됩니다.
pymalloc은 PYMEM_DOMAIN_MEM(예: PyMem_Malloc())과 PYMEM_DOMAIN_OBJ(예: PyObject_Malloc()) 도메인의 기본 할당자입니다.
아레나 할당자는 다음 함수를 사용합니다:
윈도우에서
VirtualAlloc()과VirtualFree(),사용할 수 있으면
mmap()과munmap()그렇지 않으면
malloc()과free()
Python이 --without-pymalloc 옵션으로 구성된 경우 이 할당자는 비활성화됩니다. 또한 PYTHONMALLOC 환경 변수(예: PYTHONMALLOC=malloc)를 사용하여 런타임에 비활성화할 수도 있습니다.
일반적으로 C 코드 내부의 낮은 수준의 버그를 찾아내는 데 도움이 되는 AddressSanitizer (--with-address-sanitizer)로 Python을 빌드할 때는 pymalloc 할당자를 비활성화하는 것이 좋습니다.
pymalloc 아레나 할당자 사용자 정의¶
Added in version 3.4.
-
type PyObjectArenaAllocator¶
아레나 할당자를 기술하는 데 사용되는 구조체. 이 구조체에는 세 개의 필드가 있습니다:
필드
의미
void *ctx첫 번째 인자로 전달된 사용자 컨텍스트
void* alloc(void *ctx, size_t size)size 바이트의 아레나를 할당합니다
void free(void *ctx, void *ptr, size_t size)아레나를 해제합니다
-
void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)¶
아레나 할당자를 얻습니다.
-
void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)¶
아레나 할당자를 설정합니다.
mimalloc 할당자¶
Added in version 3.13.
Python은 기본 플랫폼 지원이 가능할 때 mimalloc <https://github.com/microsoft/mimalloc/> 할당자를 지원합니다. mimalloc은 Koka 및 Lean 언어의 런타임 시스템을 위해 Daan Leijen이 개발한 우수한 성능 특성을 가진 범용 할당자입니다.
작은 객체 (512바이트 이하)에 최적화된 pymalloc 와 달리, mimalloc은 모든 크기의 할당을 처리합니다.
free-threaded 빌드에서 mimalloc은 PYMEM_DOMAIN_MEM 및 PYMEM_DOMAIN_OBJ 도메인에 기본이며 필수 할당자입니다. free-threaded 빌드에서는 비활성화할 수 없습니다. free-threaded 빌드는 스레드마다 mimalloc 힙을 사용하여 대부분의 경우 잠금 없이 할당 및 해제를 진행할 수 있게 합니다.
기본(non-free-threaded) 빌드에서는 mimalloc을 사용할 수 있지만 기본 할당자는 아닙니다. PYTHONMALLOC\ =mimalloc (또는 debug hooks 포함을 위해 mimalloc_debug) 을 사용하여 런타임에 선택할 수 있습니다. 구성 옵션 --without-mimalloc 을 사용하여 빌드 시간에 비활성화할 수 있지만, 이 옵션은 --disable-gil 과 결합할 수 없습니다.
tracemalloc C API¶
Added in version 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 버퍼가 첫 번째 함수 집합을 사용하여 파이썬 힙에서 할당되도록 다시 작성되었습니다:
PyObject *res;
char *buf = (char *) PyMem_Malloc(BUFSIZ); /* I/O 용 */
if (buf == NULL)
return PyErr_NoMemory();
/* ...buf 를 사용하는 I/O 연산을 수행합니다... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* PyMem_Malloc 으로 할당했습니다 */
return res;
형 지향 함수 집합을 사용하는 같은 코드입니다:
PyObject *res;
char *buf = PyMem_New(char, BUFSIZ); /* I/O 용 */
if (buf == NULL)
return PyErr_NoMemory();
/* ...buf 를 사용하는 I/O 연산을 수행합니다... */
res = PyBytes_FromString(buf);
PyMem_Free(buf); /* 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_Free() */
Python 힙에서 원시 메모리 블록을 처리하는 함수들 외에도, Python의 객체들은 PyObject_New, PyObject_NewVar 그리고 PyObject_Free() 로 할당 및 해제됩니다.
이것들은 C로 새로운 객체 형을 정의하고 구현하는 것에 대한 다음 장에서 설명될 것입니다.