Skip to main content

Include/internal/pycore_pymem.h

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_pymem.h

This header is the internal face of CPython's layered memory allocator. The public pymem.h exposes PyMem_Malloc and friends; this header exposes the machinery underneath: allocator structs with function-pointer tables, arena management, profiling hooks, and setup utilities.

Three allocation domains are defined:

  • PYMEM_DOMAIN_RAW -- direct OS malloc. Used for interpreter bootstrap and for allocations that must not be redirected by a custom allocator.
  • PYMEM_DOMAIN_MEM -- pymalloc, a pool-and-arena allocator tuned for objects under 512 bytes. Sits on top of RAW.
  • PYMEM_DOMAIN_OBJ -- the object allocator. In a standard build this is pymalloc again; in a free-threaded build it is routed through mimalloc.

The central struct _PyMemAllocatorEx holds four function pointers (malloc, calloc, realloc, free) plus a void *ctx so that custom allocators set via PyMem_SetAllocator can carry their own state.

Map

SymbolKindNotes
PYMEM_DOMAIN_RAWenum / int constantOS-level allocation.
PYMEM_DOMAIN_MEMenum / int constantpymalloc pool allocator.
PYMEM_DOMAIN_OBJenum / int constantObject allocator (pymalloc or mimalloc).
_PyMemAllocatorExstructFour function pointers plus ctx.
_PyObjectArenaAllocatorstructalloc / free pair for arena pages.
_PyMem_ArenaAllocfunction declAllocates one arena page from the OS.
_PyMem_ArenaFreefunction declReturns an arena page to the OS.
_PyMem_SetupAllocatorsfunction declInstalls the default allocator set.
_PyTraceMalloc_Trackfunction declRecords an allocation for tracemalloc.
_PyTraceMalloc_Untrackfunction declRemoves a tracked allocation.
_PyMem_GetCurrentAllocatorNamefunction declReturns "pymalloc", "malloc", etc.

Reading

_PyMemAllocatorEx

Every domain is represented by one instance of this struct. Swapping a custom allocator in via PyMem_SetAllocator replaces the function pointers for the chosen domain:

// Include/internal/pycore_pymem.h (abridged)
typedef struct {
void *ctx;
void *(*malloc) (void *ctx, size_t size);
void *(*calloc) (void *ctx, size_t nelem, size_t elsize);
void *(*realloc) (void *ctx, void *ptr, size_t new_size);
void (*free) (void *ctx, void *ptr);
} _PyMemAllocatorEx;

The ctx field lets a custom allocator carry private state without a global variable. CPython's own allocators pass NULL and ignore it.

Arena allocation

pymalloc manages fixed-size arena pages (256 KB by default). The arena layer is separate from the pool layer so the OS-facing calls can be intercepted independently:

// Include/internal/pycore_pymem.h
typedef struct {
void *ctx;
void *(*alloc) (void *ctx, size_t size);
void (*free) (void *ctx, void *ptr, size_t size);
} _PyObjectArenaAllocator;

PyAPI_FUNC(void) _PyMem_ArenaAlloc(void);
PyAPI_FUNC(void) _PyMem_ArenaFree(void *ptr, size_t size);

_PyMem_ArenaAlloc calls mmap on POSIX systems and VirtualAlloc on Windows. The size parameter passed to _PyMem_ArenaFree lets the OS reclaim the exact mapping rather than scanning a free list.

tracemalloc hooks

_PyTraceMalloc_Track is called by the allocator wrappers after every successful allocation when tracemalloc is active. It records the pointer, size, and current call stack into a hash table keyed by domain:

// CPython: Modules/_tracemalloc.c (simplified call site)
void *ptr = raw_malloc(size);
if (ptr != NULL && _PyTraceMalloc_IsEnabled()) {
_PyTraceMalloc_Track(PYMEM_DOMAIN_OBJ, (uintptr_t)ptr, size);
}

The symmetric _PyTraceMalloc_Untrack call is inserted at every free site. Forgetting either side produces phantom leaks or spurious untrack errors in tracemalloc.get_traced_memory().

gopy mirror

gopy does not port this header. Go's garbage collector tracks all allocations automatically. There are no arena pages, no domain splits, and no tracemalloc equivalent needed.

If a future gopy diagnostic tool wants allocation statistics at the Python-object level, the appropriate hook would be a Go runtime.SetFinalizer or a custom sync.Pool, not a port of pymalloc.

CPython 3.14 changes

  • The PYMEM_DOMAIN_OBJ path gained a branch for the free-threaded build that routes through mimalloc instead of pymalloc (see pycore_mimalloc.h).
  • _PyMem_GetCurrentAllocatorName was added in 3.13 and stabilised in 3.14; it returns a string suitable for embedding in diagnostics.
  • _PyTraceMalloc_Track received an additional domain argument in 3.12 and kept that signature through 3.14.
  • Arena size is now a runtime parameter (_Py_GetConfig()->arena_size) rather than a compile-time constant, enabling embedders to tune memory usage without recompilation.