CPython Memory Allocator Hierarchy & PyMalloc
Master CPython internal memory architecture: allocation size domains, the PyMalloc 3-tier hierarchy (Arenas, Pools, Blocks), size classes, and O(1) free-list allocation.
Python creates and destroys millions of small objects (such as integers, short strings, and tuple pairs) throughout a typical application lifecycle.
Routing all of these tiny allocations directly to the operating system's general-purpose heap allocator (malloc and free) would cause severe memory fragmentation and heavy kernel syscall overhead.
To achieve high throughput, CPython incorporates PyMalloc: a specialized small-object memory allocator layered directly above the system heap.
Allocations of 512 bytes or smaller are handled by PyMalloc; larger requests route to system malloc.
1. The Three Allocation Domains#
CPython divides memory allocations into three distinct size domains based on request size bytes:
2. The 3-Tier PyMalloc Hierarchy#
PyMalloc structures heap memory into a strict three-tier hierarchy: Arenas, Pools, and Blocks.
1. Blocks (The Allocation Unit)#
A block is a chunk of contiguous memory formatted to a specific size class. PyMalloc defines 64 size classes, spanning 8 bytes to 512 bytes in increments of 8:
2. Pools (The Page Unit)#
A pool is a single virtual memory page. Every pool is dedicated exclusively to one single size class. A pool configured for 32-byte blocks contains only 32-byte blocks. Each pool is managed by a pool_header struct containing:
- Size class index.
- Number of free blocks available.
freeblock: Pointer to the head of a singly-linked list of available unallocated blocks.
3. Arenas (The Operating System Chunk)#
An arena is a large memory block requested from the operating system via malloc(). Each arena contains exactly 64 pools (). Arenas are tracked in a doubly-linked list sorted by available free pools.
3. Fast Path Allocation: Mechanics#
How does PyMalloc allocate a small object in constant time?
Step 1: Compute Size Class Index
Given request size , PyMalloc calculates size class index .
Step 2: Query `usedpools` Array
PyMalloc maintains a global lookup array usedpools containing pointers to active pools that have available space for each size class. It looks up pool = usedpools[I * 2].
Step 3: Pop from Singly-Linked Freeblock List
The pool->freeblock pointer references the first available memory block in the pool. PyMalloc extracts this block, advances freeblock to the next block, and returns the pointer immediately:
12block = pool->freeblock; pool->freeblock = *(uchar **)block;
This fast path executes in just a handful of CPU instructions without acquiring OS kernel mutex locks!
4. Pool Occupancy & Memory Deallocation#
When an object is freed, PyMalloc returns the block to the pool's freeblock list:
Where pool occupancy ratio is:
When all pools inside an arena become empty ( for all 64 pools), PyMalloc releases the entire 256 KB arena back to the operating system kernel via free().
5. Concept Verification#
Test your understanding of CPython memory allocators:
What is the maximum object request size handled by CPython's specialized PyMalloc allocator before falling back to system malloc?
How does PyMalloc achieve O(1) allocation speed for small objects?