PEP 445 – Python 메모리 할당자를 사용자 지정하기 위한 새 API 추가
- Author:
- Victor Stinner <vstinner at python.org>
- BDFL-Delegate:
- Antoine Pitrou <solipsis at pitrou.net>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 15-Jun-2013
- Python-Version:
- 3.4
- Resolution:
- Python-Dev message
Table of Contents
- 초록
- 근거
- 제안
- 예제
- 성능
- 거부된 대안
- 메모리 할당자를 가져오고 설정하기 위한 더 구체적인 함수
- PyMem_Malloc()이 기본적으로 PyMem_RawMalloc()을 재사용하도록 변경합니다.
- 새로운 PYDEBUGMALLOC 환경 변수를 추가합니다.
- 사용자 정의 가능한 할당자를 얻기 위해 매크로를 사용합니다.
- C 파일 이름과 줄 번호를 전달합니다.
- GIL 없이 사용하는 PyMem_Malloc()
- PyMem_RawMalloc()을 추가하지 마십시오.
- 메모리 사용량을 분석하려면 기존 디버그 도구를 사용하십시오.
- msize() 함수를 추가하십시오.
- 컨텍스트 인자를 사용하지 않습니다.
- 외부 라이브러리
- 메모리 할당자
- 링크
- Copyright
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 Python 메모리 할당자를 사용자 지정하기 위한 새로운 응용 프로그래밍 인터페이스(API)를 제안합니다. 이 PEP를 준수해야 하는 유일한 구현은 CPython이지만, 다른 구현은 호환성을 유지하거나 유사한 방식을 재사용할 수 있습니다.
근거
사용 사례:
- Python 메모리를 애플리케이션의 메모리와 격리하거나 Python 사용에 최적화된 다른 메모리 할당자를 사용하려는 Python 임베딩 애플리케이션
- 메모리가 부족하고 CPU가 느린 임베디드 장치에서 실행되는 Python입니다. 효율성을 높이거나 장치의 모든 메모리에 액세스하기 위해 사용자 지정 메모리 할당자를 사용할 수 있습니다.
- 메모리 할당자용 디버그 도구:
- 메모리 사용량을 추적합니다(메모리 누수 찾기).
- 메모리 할당 위치(파일 이름과 줄 번호)와 메모리 블록의 크기를 확인합니다.
- 버퍼 언더플로, 버퍼 오버플로 및 Python 할당자 API의 오용을 감지합니다(Redesign Debug Checks on Memory Block Allocators as Hooks 참조).
MemoryError예외 처리를 테스트하기 위해 메모리 할당이 실패하도록 강제합니다.
제안
새 함수 및 구조체
- 새로운 GIL 비종속( GIL을 보유할 필요가 없는) 메모리 할당자를 추가합니다.
void* PyMem_RawMalloc(size_t size)void* PyMem_RawRealloc(void *ptr, size_t new_size)void PyMem_RawFree(void *ptr)- 새로 할당된 메모리는 어떤 방식으로도 초기화되지 않습니다.
- 0바이트를 요청하면 가능한 경우
PyMem_Malloc(1)을 호출한 것처럼 서로 구별되는, NULL이 아닌 포인터를 반환합니다.
- 새로운
PyMemAllocator구조체를 추가합니다.:typedef struct { /* user context passed as the first argument to the 3 functions */ void *ctx; /* allocate a memory block */ void* (*malloc) (void *ctx, size_t size); /* allocate or resize a memory block */ void* (*realloc) (void *ctx, void *ptr, size_t new_size); /* release a memory block */ void (*free) (void *ctx, void *ptr); } PyMemAllocator;
- Python 할당자 도메인을 선택하기 위한 새로운
PyMemAllocatorDomain열거형을 추가합니다. 도메인:PYMEM_DOMAIN_RAW:PyMem_RawMalloc(),PyMem_RawRealloc()및PyMem_RawFree()PYMEM_DOMAIN_MEM:PyMem_Malloc(),PyMem_Realloc()및PyMem_Free()PYMEM_DOMAIN_OBJ:PyObject_Malloc(),PyObject_Realloc()및PyObject_Free()
- 메모리 블록 할당자를 가져오고 설정하는 새 함수를 추가합니다.
void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocator *allocator)void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocator *allocator)- 새 할당자는 0바이트를 요청할 때 서로 구별되는, NULL이 아닌 포인터를 반환해야 합니다.
PYMEM_DOMAIN_RAW도메인에서는 할당자가 스레드 안전해야 합니다. 할당자가 호출될 때 GIL이 유지되지 않습니다.
- 새로운
PyObjectArenaAllocator구조체를 추가하십시오.:typedef struct { /* user context passed as the first argument to the 2 functions */ void *ctx; /* allocate an arena */ void* (*alloc) (void *ctx, size_t size); /* release an arena */ void (*free) (void *ctx, void *ptr, size_t size); } PyObjectArenaAllocator;
- pymalloc에서 사용하는 아레나 할당자를 가져오고 설정하는 새 함수를 추가하십시오:
void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)
- 메모리 할당자가
PyMem_SetAllocator()로 교체될 때 메모리 할당자에 디버그 검사를 다시 설치하는 새 함수를 추가하십시오:void PyMem_SetupDebugHooks(void)- 모든 메모리 블록 할당자에 디버그 후크를 설치하십시오. 이 함수는 여러 번 호출할 수 있으며, 후크는 한 번만 설치됩니다.
- Python이 디버그 모드로 컴파일되지 않았다면 이 함수는 아무 작업도 수행하지 않습니다.
- 메모리 블록 할당자는 size가
PY_SSIZE_T_MAX보다 크면 항상 NULL을 반환합니다. 이 검사는 내부 함수를 호출하기 전에 수행됩니다.
Note
pymalloc 할당자는 수명이 짧은 512바이트 미만의 객체에 최적화되어 있습니다. 이 할당자는 “아레나”라고 하는 256KB 고정 크기의 메모리 매핑을 사용합니다.
할당자는 기본적으로 다음과 같이 설정됩니다:
PYMEM_DOMAIN_RAW,PYMEM_DOMAIN_MEM:malloc(),realloc()및free()를 사용하며, 0바이트를 요청하면malloc(1)을 호출합니다.PYMEM_DOMAIN_OBJ: 512바이트보다 큰 할당에 대해서는PyMem_Malloc()으로 대체하는 pymalloc 할당자입니다.- pymalloc 아레나 할당자: Windows에서는
VirtualAlloc()및VirtualFree()를 사용하고, 사용 가능한 경우mmap()및munmap()을 사용하며, 그렇지 않으면malloc()및free()를 사용합니다.
메모리 블록 할당자의 디버그 검사를 후크로 재설계
Python 2.3부터 Python은 디버그 모드에서 메모리 할당자에 대해 다양한 검사를 구현합니다.
- 새로 할당된 메모리는 바이트
0xCB로 채워지고, 해제된 메모리는 바이트0xDB로 채워집니다. - API 위반을 감지합니다. 예를 들어
PyMem_Malloc()으로 할당된 메모리 블록에PyObject_Free()가 호출되는 경우입니다. - 버퍼 시작 이전에 기록하는 행위(버퍼 언더플로)를 감지합니다.
- 버퍼 끝 이후에 기록하는 행위(버퍼 오버플로)를 감지합니다.
Python 3.3에서는 매크로를 사용하여 PyMem_Malloc(), PyMem_Realloc(), PyMem_Free(), PyObject_Malloc(), PyObject_Realloc() 및 PyObject_Free()를 교체하는 방식으로 검사를 설치합니다. 새 할당자는 더 큰 버퍼를 할당하고, 버퍼 언더플로와 버퍼 오버플로 및 해제 후 사용을 감지하기 위해 패턴을 기록합니다(버퍼를 바이트 0xDB로 채움). 메모리를 할당할 때 원래 PyObject_Malloc() 함수를 사용합니다. 따라서 PyMem_Malloc() 및 PyMem_Realloc()은 간접적으로 PyObject_Malloc() 및 PyObject_Realloc()을 호출합니다.
이 PEP는 디버그 모드에서 기존 할당자에 대한 후크로 디버그 검사를 재설계합니다. 후크가 없는 호출 추적의 예는 다음과 같습니다:
PyMem_RawMalloc()=>_PyMem_RawMalloc()=>malloc()PyMem_Realloc()=>_PyMem_RawRealloc()=>realloc()PyObject_Free()=>_PyObject_Free()
후크가 설치된 경우의 호출 추적(디버그 모드):
PyMem_RawMalloc()=>_PyMem_DebugMalloc()=>_PyMem_RawMalloc()=>malloc()PyMem_Realloc()=>_PyMem_DebugRealloc()=>_PyMem_RawRealloc()=>realloc()PyObject_Free()=>_PyMem_DebugFree()=>_PyObject_Free()
그 결과 이제 릴리스 모드와 디버그 모드 모두에서 PyMem_Malloc() 및 PyMem_Realloc()은 malloc() 및 realloc()을 호출하며, 디버그 모드에서 PyObject_Malloc() 및 PyObject_Realloc()을 호출하지 않습니다.
하나 이상의 메모리 할당자를 PyMem_SetAllocator()로 교체한 경우, 새 할당자 위에 디버그 후크를 다시 설치하려면 PyMem_SetupDebugHooks() 함수를 호출해야 합니다.
더 이상 malloc()을 직접 호출하지 마십시오.
크기가 512바이트 이상이면 PyObject_Malloc()은 malloc() 대신 PyMem_Malloc()으로 대체 실행되며, PyObject_Realloc()은 realloc() 대신 PyMem_Realloc()으로 대체 실행됩니다.
malloc()에 대한 직접 호출은 PyMem_Malloc()으로 교체하며, GIL을 보유하지 않은 경우에는 PyMem_RawMalloc()으로 교체합니다.
zlib 또는 OpenSSL과 같은 외부 라이브러리는 PyMem_Malloc() 또는 PyMem_RawMalloc()을 사용하여 메모리를 할당하도록 구성할 수 있습니다. 라이브러리의 할당자를 객체별 방식이 아니라 전역적으로만 교체할 수 있다면, Python이 애플리케이션에 임베드된 경우에는 이를 교체해서는 안 됩니다.
“메모리 사용량 추적” 사용 사례에서는 정확한 보고서를 작성하기 위해 외부 라이브러리에서 할당된 메모리도 추적하는 것이 중요합니다. 이러한 할당은 클 수 있으며(예를 들어 MemoryError 예외를 발생시킬 수 있음), 그렇지 않으면 메모리 사용량 보고서에서 누락되기 때문입니다.
예제
사용 사례 1: 메모리 할당자 교체, pymalloc 유지
메모리 블록마다 2바이트, pymalloc 아레나마다 10바이트를 낭비하는 더미 예제:
#include <stdlib.h>
size_t alloc_padding = 2;
size_t arena_padding = 10;
void* my_malloc(void *ctx, size_t size)
{
int padding = *(int *)ctx;
return malloc(size + padding);
}
void* my_realloc(void *ctx, void *ptr, size_t new_size)
{
int padding = *(int *)ctx;
return realloc(ptr, new_size + padding);
}
void my_free(void *ctx, void *ptr)
{
free(ptr);
}
void* my_alloc_arena(void *ctx, size_t size)
{
int padding = *(int *)ctx;
return malloc(size + padding);
}
void my_free_arena(void *ctx, void *ptr, size_t size)
{
free(ptr);
}
void setup_custom_allocator(void)
{
PyMemAllocator alloc;
PyObjectArenaAllocator arena;
alloc.ctx = &alloc_padding;
alloc.malloc = my_malloc;
alloc.realloc = my_realloc;
alloc.free = my_free;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
/* leave PYMEM_DOMAIN_OBJ unchanged, use pymalloc */
arena.ctx = &arena_padding;
arena.alloc = my_alloc_arena;
arena.free = my_free_arena;
PyObject_SetArenaAllocator(&arena);
PyMem_SetupDebugHooks();
}
사용 사례 2: 메모리 할당자 교체, pymalloc 재정의
수명이 짧고 512바이트보다 작은 객체의 할당에 최적화된 전용 할당자가 있다면 pymalloc을 재정의할 수 있습니다(PyObject_Malloc()을 교체합니다).
메모리 블록마다 2바이트를 낭비하는 더미 예제:
#include <stdlib.h>
size_t padding = 2;
void* my_malloc(void *ctx, size_t size)
{
int padding = *(int *)ctx;
return malloc(size + padding);
}
void* my_realloc(void *ctx, void *ptr, size_t new_size)
{
int padding = *(int *)ctx;
return realloc(ptr, new_size + padding);
}
void my_free(void *ctx, void *ptr)
{
free(ptr);
}
void setup_custom_allocator(void)
{
PyMemAllocator alloc;
alloc.ctx = &padding;
alloc.malloc = my_malloc;
alloc.realloc = my_realloc;
alloc.free = my_free;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
PyMem_SetupDebugHooks();
}
새 할당자가 더 이상 사용하지 않으므로 pymalloc 아레나는 교체할 필요가 없습니다.
사용 사례 3: 메모리 블록 할당자에 후크 설정
모든 메모리 블록 할당자에 후크를 설정하는 예제:
struct {
PyMemAllocator raw;
PyMemAllocator mem;
PyMemAllocator obj;
/* ... */
} hook;
static void* hook_malloc(void *ctx, size_t size)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
void *ptr;
/* ... */
ptr = alloc->malloc(alloc->ctx, size);
/* ... */
return ptr;
}
static void* hook_realloc(void *ctx, void *ptr, size_t new_size)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
void *ptr2;
/* ... */
ptr2 = alloc->realloc(alloc->ctx, ptr, new_size);
/* ... */
return ptr2;
}
static void hook_free(void *ctx, void *ptr)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
/* ... */
alloc->free(alloc->ctx, ptr);
/* ... */
}
void setup_hooks(void)
{
PyMemAllocator alloc;
static int installed = 0;
if (installed)
return;
installed = 1;
alloc.malloc = hook_malloc;
alloc.realloc = hook_realloc;
alloc.free = hook_free;
PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &hook.raw);
PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &hook.mem);
PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &hook.obj);
alloc.ctx = &hook.raw;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
alloc.ctx = &hook.mem;
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
alloc.ctx = &hook.obj;
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
}
Note
메모리 할당자는 교체되지 않으므로 PyMem_SetupDebugHooks()를 호출할 필요가 없습니다. 메모리 블록 할당자에 대한 디버그 검사는 시작 시 자동으로 설치됩니다.
성능
이 PEP(issue #3329)의 구현은 Python 벤치마크 모음에서 눈에 띄는 오버헤드가 없습니다.
Python benchmarks suite의 결과(-b 2n3): 일부 테스트는 1.04배 더 빠르고, 일부 테스트는 1.04배 더 느립니다. pybench 마이크로벤치마크 결과: 전체적으로 “+0.1%” 더 느립니다(-4.9%와 +5.6% 사이의 차이).
벤치마크의 전체 출력은 issue #3329에 첨부되어 있습니다.
거부된 대안
메모리 할당자를 가져오고 설정하기 위한 더 구체적인 함수
원래는 각 할당자 도메인마다 한 쌍의 함수를 두는 더 큰 C API 함수 집합이 제안되었습니다.
void PyMem_GetRawAllocator(PyMemAllocator *allocator)void PyMem_GetAllocator(PyMemAllocator *allocator)void PyObject_GetAllocator(PyMemAllocator *allocator)void PyMem_SetRawAllocator(PyMemAllocator *allocator)void PyMem_SetAllocator(PyMemAllocator *allocator)void PyObject_SetAllocator(PyMemAllocator *allocator)
이 대안은 더 구체적인 함수를 사용해서는 일반 코드를 작성할 수 없기 때문에 거부되었습니다. 메모리 할당자 도메인마다 코드를 복제해야 합니다.
PyMem_Malloc()이 기본적으로 PyMem_RawMalloc()을 재사용하도록 변경합니다.
PyMem_Malloc()이 기본적으로 PyMem_RawMalloc()을 호출한다면, PyMem_SetAllocator(PYMEM_DOMAIN_RAW, alloc)을 호출하는 것이 간접적으로 PyMem_Malloc()도 수정하게 됩니다.
이 대안은 PyMem_SetAllocator()의 동작이 도메인에 따라 달라지기 때문에 거부되었습니다. 항상 동일하게 동작하도록 하는 편이 오류가 발생할 가능성이 낮습니다.
새로운 PYDEBUGMALLOC 환경 변수를 추가합니다.
메모리 블록 할당자에 대한 디버그 검사를 활성화하기 위해 새로운 PYDEBUGMALLOC 환경 변수를 추가하자는 제안이 있었습니다. C 코드를 작성하지 않고도 PyMem_SetupDebugHooks()를 호출하는 것과 동일한 효과를 얻을 수 있습니다. 또 다른 장점은 릴리스 모드에서도 디버그 검사를 활성화할 수 있다는 점입니다. 디버그 검사는 항상 컴파일되지만, 환경 변수가 존재하고 비어 있지 않을 때만 활성화됩니다.
이 대안은 새로운 환경 변수가 Python 초기화를 더욱 복잡하게 만들기 때문에 거부되었습니다. PEP 432는 CPython 시작 시퀀스를 단순화하려고 합니다.
사용자 정의 가능한 할당자를 얻기 위해 매크로를 사용합니다.
기본 구성에서 오버헤드가 없도록 사용자 정의 가능한 할당자는 구성 옵션이나 매크로로 활성화되는 선택적 기능이 됩니다.
이 대안은 매크로를 사용하면 새로운 할당자와 할당자 훅을 사용하기 위해 확장 모듈을 다시 컴파일해야 하기 때문에 거부되었습니다. Python이나 확장 모듈을 다시 컴파일할 필요가 없으므로 실제로 디버그 훅을 더 쉽게 사용할 수 있습니다.
C 파일 이름과 줄 번호를 전달합니다.
메모리 할당의 C 파일 이름과 줄 번호를 얻기 위해 __FILE__과 __LINE__을 사용하는 매크로로 할당자 함수를 정의합니다.
수정된 PyMemAllocator구조를 사용하는 PyMem_Malloc 매크로의 예입니다.:
typedef struct {
/* user context passed as the first argument
to the 3 functions */
void *ctx;
/* allocate a memory block */
void* (*malloc) (void *ctx, const char *filename, int lineno,
size_t size);
/* allocate or resize a memory block */
void* (*realloc) (void *ctx, const char *filename, int lineno,
void *ptr, size_t new_size);
/* release a memory block */
void (*free) (void *ctx, const char *filename, int lineno,
void *ptr);
} PyMemAllocator;
void* _PyMem_MallocTrace(const char *filename, int lineno,
size_t size);
/* the function is still needed for the Python stable ABI */
void* PyMem_Malloc(size_t size);
#define PyMem_Malloc(size) \
_PyMem_MallocTrace(__FILE__, __LINE__, size)
GC 할당자 함수도 수정해야 합니다. 예를 들어 _PyObject_GC_Malloc()은 많은 C 함수에서 사용되므로 서로 다른 유형의 객체가 동일한 할당 위치를 갖게 됩니다.
이 대안은 각 할당자에 파일 이름과 줄 번호를 전달하면 API가 더 복잡해지기 때문에 거부되었습니다. 각 할당자 함수에 컨텍스트 인자(ctx)만 전달하는 대신 새로운 인자 3개(ctx, filename, lineno)를 전달해야 합니다. GC 할당자 함수도 수정해야 하므로 얻는 이득에 비해 복잡성이 지나치게 커집니다.
GIL 없이 사용하는 PyMem_Malloc()
Python 3.3에서는 Python을 디버그 모드로 컴파일하면 PyMem_Malloc()이 간접적으로 PyObject_Malloc()을 호출하며, 이 함수는 GIL을 보유해야 합니다(스레드로부터 안전하지 않습니다). 따라서 PyMem_Malloc()은 GIL을 보유한 상태에서 호출해야 합니다.
이 PEP는 PyMem_Malloc()을 변경합니다. 이제 PyObject_Malloc()이 아니라 항상 malloc()을 호출합니다. 따라서 PyMem_Malloc()에서 “GIL을 보유해야 함”이라는 제한을 제거할 수 있습니다.
이 대안은 GIL을 보유하지 않은 상태에서 PyMem_Malloc()을 호출하도록 허용하면 자체 할당자 또는 할당자 후크를 설정한 애플리케이션이 손상될 수 있기 때문에 거부되었습니다. GIL을 보유하면 사용자 지정 할당자를 편리하게 개발할 수 있습니다. 다른 스레드를 신경 쓸 필요가 없습니다. 디버그 할당자 후크에도 편리합니다. Python 객체를 안전하게 검사할 수 있고, 보고에 C API를 사용할 수도 있습니다.
또한 메모리 할당자에서 PyGILState_Ensure()를 호출하면 예상하지 못한 동작이 발생하며, 특히 Python 시작 시와 새 Python 스레드 상태를 생성할 때 그러합니다. 사용자 지정 할당자가 GIL을 획득할 책임을 지도록 하는 것이 더 낫습니다.
PyMem_RawMalloc()을 추가하지 마십시오.
malloc()을 PyMem_Malloc()으로 교체하되, GIL을 보유한 경우에만 교체하십시오. 그렇지 않으면 malloc()을 변경하지 않은 상태로 유지하십시오.
일부 Python 함수에서는 GIL을 보유하지 않은 상태로 PyMem_Malloc()을 사용합니다. 예를 들어 Python의 main()및 Py_Main()함수는 아직 GIL이 존재하지 않는데도 PyMem_Malloc()을 호출합니다. 이 경우 PyMem_Malloc()을 malloc()(또는 PyMem_RawMalloc())으로 교체합니다.
이 대안은 메모리 사용량을 정확하게 보고하려면 PyMem_RawMalloc()이 필요하기 때문에 거부되었습니다. 메모리 사용량을 추적하기 위해 디버그 후크를 사용하는 경우, malloc()을 직접 호출하여 할당한 메모리는 추적할 수 없습니다. PyMem_RawMalloc()에 후크를 설정할 수 있으므로 GIL을 보유하지 않은 상태에서 할당한 메모리를 포함하여 Python이 할당한 모든 메모리를 추적할 수 있습니다.
메모리 사용량을 분석하려면 기존 디버그 도구를 사용하십시오.
메모리 사용량을 분석할 수 있는 기존 디버그 도구는 많습니다. 몇 가지 예로는 Valgrind, Purify, Clang AddressSanitizer, failmalloc 등이 있습니다.
문제는 메모리 포인터와 관련된 Python 객체를 가져와 그 타입 및/또는 내용을 읽는 것입니다. 또 다른 문제는 메모리 할당의 출처를 가져오는 것입니다. C 역추적은 일반적으로 쓸모가 없습니다(__FILE__및 __LINE__을 사용하는 매크로와 같은 이유입니다. Pass the C filename and line number를 참조하십시오). Python 파일 이름과 줄 번호 또는 Python 역추적이 훨씬 더 유용합니다.
이 대안은 기존 도구가 이러한 정보를 수집하기 위해 Python 내부를 검사할 수 없기 때문에 거부되었습니다. GIL을 보유한 상태에서 호출되는 할당자에 후크를 설정할 수 있으면 Python 내부에서 유용한 데이터를 많이 수집할 수 있습니다.
msize() 함수를 추가하십시오.
PyMemAllocator및 PyObjectArenaAllocator구조체에 다른 함수를 추가하십시오.:
size_t msize(void *ptr);
이 함수는 메모리 블록 또는 메모리 매핑의 크기를 반환합니다. 함수가 구현되지 않았거나 포인터를 알 수 없는 경우(예: NULL 포인터) (size_t)-1을 반환하십시오.
Windows에서는 _msize()및 VirtualQuery()를 사용하여 이 함수를 구현할 수 있습니다.
이 함수는 메모리 사용량을 추적하는 후크를 구현하는 데 사용할 수 있습니다. 할당자의 free()메서드는 메모리 블록의 주소만 가져오는 반면, 메모리 사용량을 갱신하려면 메모리 블록의 크기가 필요합니다.
추가적인 msize()함수는 이를 구현하는 플랫폼이 거의 없기 때문에 거부되었습니다. 예를 들어 GNU libc를 사용하는 Linux는 메모리 블록의 크기를 가져오는 함수를 제공하지 않습니다. msize()는 현재 Python 소스 코드에서 사용되지 않습니다. 이 함수는 메모리 사용량을 추적하는 데만 사용되며 API를 더 복잡하게 만들 뿐입니다. 디버그 후크가 내부적으로 이 함수를 구현할 수 있으므로 PyMemAllocator및 PyObjectArenaAllocator구조체에 이를 추가할 필요가 없습니다.
컨텍스트 인자를 사용하지 않습니다.
할당자 함수의 시그니처를 단순화하고 컨텍스트 인자를 제거합니다:
void* malloc(size_t size)void* realloc(void *ptr, size_t new_size)void free(void *ptr)
할당자 훅을 PyMem_SetAllocator() 및 PyObject_SetAllocator(), 또는 PyMem_SetRawAllocator()에 재사용할 가능성이 높지만, 훅은 할당자에 따라 다른 함수를 호출해야 합니다. 컨텍스트는 동일한 사용자 정의 할당자 또는 훅을 서로 다른 Python 할당자에 재사용하는 편리한 방법입니다.
C++에서는 컨텍스트를 this를 전달하는 데 사용할 수 있습니다.
외부 라이브러리
메모리 할당자를 사용자 정의하는 데 사용되는 API의 예입니다.
Python에서 사용되는 라이브러리:
- OpenSSL: 메모리 관리 함수를 전역적으로 설정하는 CRYPTO_set_mem_functions()
- expat: parserCreate()는 인스턴스별 메모리 핸들러를 가집니다.
- zlib: zlib 1.2.8 Manual, 불투명 포인터를 전달합니다.
- bz2: bzip2 and libbzip2, version 1.0.5, 불투명 포인터를 전달합니다.
- lzma: LZMA SDK - How to Use, 불투명 포인터를 전달합니다.
- lipmpdec: 불투명 포인터가 없습니다(고전적인 malloc API).
기타 라이브러리:
- glib: g_mem_set_vtable()
- libxml2: xmlGcMemSetup(), 전역입니다.
- Oracle의 OCI: Oracle Call Interface Programmer’s Guide, Release 2 (9.2), 불투명 포인터를 전달합니다.
이 PEP의 새로운 ctx 매개변수는 zlib 및 Oracle의 OCI 라이브러리 API에서 영감을 받았습니다.
메모리 할당자를 훅으로 연결하는 다른 접근 방식을 사용하는 GNU libc: Memory Allocation Hooks도 참고하십시오.
메모리 할당자
C 표준 라이브러리는 잘 알려진 malloc() 함수를 제공합니다. 해당 구현은 플랫폼과 C 라이브러리에 따라 달라집니다. GNU C 라이브러리는 “Doug Lea’s Malloc”(dlmalloc)을 기반으로 수정된 ptmalloc2를 사용합니다. FreeBSD는 jemalloc을 사용합니다. Google은 gperftools의 일부인 tcmalloc을 제공합니다.
malloc()은 두 종류의 메모리, 즉 힙과 메모리 매핑을 사용합니다. 메모리 매핑은 일반적으로 큰 할당(예: 256KB보다 큰 할당)에 사용되는 반면, 힙은 작은 할당에 사용됩니다.
UNIX에서는 힙이 brk() 및 sbrk() 시스템 호출로 처리되며, 연속적입니다. Windows에서는 힙이 HeapAlloc()로 처리되며, 비연속적일 수 있습니다. 메모리 매핑은 UNIX에서는 mmap()로, Windows에서는 VirtualAlloc()로 처리되며, 비연속적일 수 있습니다.
메모리 매핑을 해제하면 메모리가 즉시 시스템에 반환됩니다. UNIX에서는 해제된 블록이 힙의 끝에 위치한 경우에만 힙 메모리가 시스템에 반환됩니다. 그렇지 않으면 해제된 메모리 뒤에 위치한 모든 메모리도 해제될 때에만 메모리가 시스템에 반환됩니다.
힙에 메모리를 할당하기 위해 할당자는 빈 공간을 재사용하려고 합니다. 충분히 큰 연속 공간이 없으면 필요한 크기보다 더 많은 빈 공간이 있더라도 힙을 확장해야 합니다. 이 문제를 “메모리 단편화”라고 합니다. 시스템에서 확인되는 메모리 사용량이 실제 사용량보다 높습니다. Windows에서는 사용 가능한 연속 메모리가 충분하지 않으면 HeapAlloc()이 VirtualAlloc()으로 새 메모리 매핑을 생성합니다.
CPython에는 512바이트보다 작은 할당을 위한 pymalloc 할당자가 있습니다. 이 할당자는 수명이 짧은 작은 객체에 최적화되어 있습니다. 이 할당자는 “아레나”라고 하는 256KB의 고정 크기 메모리 매핑을 사용합니다.
기타 할당자:
- Windows는 Low-fragmentation Heap을 제공합니다.
- Linux 커널은 slab allocation 을 사용합니다.
- glib 라이브러리에는 Memory Slice API가 있습니다: 동일한 크기의 메모리 청크 그룹을 할당하는 효율적인 방법
이 PEP를 사용하면 메모리 사용량(할당 횟수, 할당 크기, 객체 수명 등)에 따라 애플리케이션에서 사용할 메모리 할당자를 정확하게 선택할 수 있습니다.
링크
메모리 할당과 관련된 CPython 이슈:
- 이슈 #3329: 메모리 할당자를 커스터마이즈하는 새로운 API 추가
- 이슈 #13483: 메모리 아레나를 할당하기 위해 VirtualAlloc 사용
- 이슈 #16742: PyOS_Readline이 GIL을 해제하고 스레드 안전하지 않은 PyOS_StdioReadline을 호출함
- 이슈 #18203: malloc() 호출을 PyMem_Malloc() 또는 PyMem_RawMalloc()으로 교체
- 이슈 #18227: zlib나 OpenSSL 같은 외부 라이브러리에서 Python 메모리 할당자 사용
Python 애플리케이션의 메모리 사용량을 분석하는 프로젝트:
Copyright
This document has been placed into the public domain.