PyMem_Malloc vs PyObject_Malloc in CPython
CPython employs a multi-tiered memory architecture designed to
optimize allocation speed and reduce memory fragmentation for dynamic
workloads. Within this hierarchy, PyMem_Malloc and
PyObject_Malloc serve distinct roles:
PyMem_Malloc acts as the memory allocator for
general-purpose C-level data buffers and structures, while
PyObject_Malloc is the specialized allocator optimized for
Python object lifecycles through the pymalloc allocator.
Understanding the boundaries between these allocators is critical for
writing correct, performant C extensions and avoiding memory
corruption.
The CPython Memory Hierarchy
CPython structures its memory management into four primary layers, moving from low-level system calls to high-level Python abstractions:
- Layer 0 (System Allocator): Standard C library
allocators (
malloc,calloc,realloc,free). - Layer 1 (Raw Memory /
PyMem_Raw*): A wrapper around the OS allocator suitable for memory allocation when the Global Interpreter Lock (GIL) is not held, or for low-level internal runtime needs. - Layer 2 (Python Memory /
PyMem_*): Designed for intermediate internal buffers, strings, and C extensions that do not represent full Python objects. - Layer 3 (Object Allocator /
PyObject_*): Tailored specifically for managingPyObjectinstances and small memory blocks.
PyMem_Malloc: Buffer and Intermediate Memory
PyMem_Malloc provides memory management for internal
C-level buffers used by the interpreter and external extension modules.
Its primary characteristics include:
- Scope: It is intended for raw data buffers, temporary work areas, arrays of C types, or struct instances that are not exposed directly as Python objects.
- GIL Requirement: The calling thread must hold the Python Global Interpreter Lock (GIL).
- Customizability: It delegates to the Python memory
domain (
PYMEM_DOMAIN_MEM), which can be hooked or redirected via custom allocators using Python's runtime memory tracing hooks (such astracemalloc).
PyObject_Malloc: The Small Object Allocator
PyObject_Malloc operates at the highest tier of the
C-level hierarchy and is heavily optimized for Python’s runtime object
allocation patterns:
- Scope: It is designed specifically for allocating
Python objects (
PyObjectand its subtypes). - The
pymallocMechanism: For requests of 512 bytes or fewer,PyObject_Mallocuses CPython’s custom small-object allocator (pymalloc).pymallocobtains 256 KB chunks from the operating system called "arenas," divides them into 4 KB "pools," and subdivides pools into fixed-size "blocks." This prevents heap fragmentation and drastically speeds up allocation for short-lived Python objects. - Fallback Behavior: If an allocation request exceeds
512 bytes,
PyObject_Mallocautomatically falls back to the underlying general-purpose memory allocator. - GIL Requirement: Because
pymallocpools and free lists are not inherently thread-safe across threads,PyObject_Mallocstrictly requires the GIL to be held.
Key Differences Between the Two
- Semantic Purpose:
PyMem_Mallocis for arbitrary non-object memory associated with Python logic, whereasPyObject_Mallocis semantically reserved for Python objects managed by the runtime and garbage collector. - Convergence in Modern CPython: Since Python 3.6,
the default allocator for
PyMem_Mallochas been unified withpymallocto improve performance for byte buffers. Despite sharing the underlying engine by default, they remain logically separate domains (PYMEM_DOMAIN_MEMvs.PYMEM_DOMAIN_OBJ), meaning hooks, debug wrappers, and tracking tools differentiate between the two. - Pairing and Safety: Memory allocated with a
specific function must be released using its corresponding deallocator.
Memory allocated via
PyMem_Mallocmust be freed withPyMem_Free, while memory fromPyObject_Mallocmust be released viaPyObject_Free. Mixing these allocators creates undefined behavior, heap corruption, and crashes.