CPython Memory Allocator: pymalloc Arenas, Pools, and Size Classes
- Last updated
- Prerequisites:
- Python object memory model
- Operating system `malloc`/`free`
- python
- cpython
- memory
- pymalloc
- arenas
- pools
- allocation
Read at your depth
The practical view
CPython uses a dedicated small-object allocator called `pymalloc` for allocations `<= 512 bytes` (which covers 95%+ of typical Python integers, strings, tuples, and dict entries). `pymalloc` organizes memory hierarchically into **Arenas** (256KB chunks requested from OS `malloc`), **Pools** (4KB blocks inside arenas corresponding to a single size class), and **Blocks** (small chunks in 8-byte increments from 8 to 512 bytes). Objects larger than 512 bytes bypass `pymalloc` and use system `malloc`.
The same idea in other frameworks
CPython `pymalloc` (Arenas 256KB -> Pools 4KB -> Blocks <=512B).Go runtime `mcache` / `mcentral` / `mheap` size-class allocator (TCMalloc derivative).JVM TLAB (Thread-Local Allocation Buffers) bump pointer allocation.`jemalloc` / `mimalloc` size-class arena allocators.Legacy vs modern
Small Object Allocation (pymalloc fastpath) vs Large Object Allocation (System malloc)
Small Python objects use fast `pymalloc` pools; objects >512B incur system `malloc` overhead.
bytearray(1024) # ❌ Bypasses pymalloc, calls system malloc()items = [i for i in range(1000)] # ✅ NEW: Fast 8-instruction pymalloc pool bumpInterview gotchas
Context
This is the classic production Python memory debugging interview question.
The mechanical answer
Because `pymalloc` operates in **256KB Arenas**. An arena can only be released back to the OS via `free()` if **every single pool inside that arena is 100% empty**. If you allocate 1,000,000 objects and then delete 999,000 of them, but the remaining 1,000 surviving objects happen to be scattered across different arenas (1 object per arena), **none of the arenas can be freed**, and Python's RSS memory usage reported by the OS will remain high.
Trap
Calling `gc.collect()` or `del my_list` frees objects inside `pymalloc` pools for Python reuse, but does not guarantee the OS will see lower process RSS memory.
Context
Checks precision regarding CPython internal constants.
The mechanical answer
The threshold is **512 bytes** (`SMALL_REQUEST_THRESHOLD` in `obmalloc.c`). Any allocation request of 513 bytes or larger bypasses `pymalloc` and delegates directly to the standard C library `malloc()`.
Trap
Prior to Python 3.8, the default threshold was 256 bytes; in modern Python (3.8+ / 3.12+), it is 512 bytes.