Skip to main content

Python/freelist.c

cpython 3.14 @ ab2d84fe1023/Python/freelist.c

Free list caches for the most frequently allocated object types: float, tuple (up to eight elements), list, dict entry arrays, and interpreter frames. Each cache is a singly-linked list of pre-allocated, reference-count-zero blocks keyed by size. When the allocator for one of these types needs a block, it pops from the appropriate list instead of calling PyObject_Malloc; when a block's reference count drops to zero, tp_dealloc pushes it back rather than freeing it. The result is that short-lived temporaries, which dominate most Python workloads, never reach the OS allocator.

All lists are stored on _PyFreeListState, a struct embedded in _PyInterpreterState. This makes each interpreter's free lists independent; there is no lock protecting them, and cross-interpreter object sharing is detected and rejected elsewhere. _Py_ClearFreeLists drains all lists at interpreter shutdown and at gc.collect() time to return memory to the OS.

Map

LinesSymbolRolegopy
1-40includes / freelist_state accessorPull in _PyFreeListState from pycore_freelist.h; define the inline _Py_freelists_GET() accessor.objects/freelist.go:freelistState
40-80_PyFloat_FromDouble fast path, _PyFloat_ExactDeallocPop / push float block; fall through to PyObject_Malloc on miss.objects/freelist.go:FloatFromDouble
80-130_PyTuple_FromArray, _PyTuple_MaybeUntrack, tuple_alloc, tuple_freePer-size tuple free lists (0 to 8 items).objects/freelist.go:TupleAlloc
130-165PyList_New fast path, list_dealloc contributionSingle free list for PyListObject headers (not the item array).objects/freelist.go:ListAlloc
165-200_PyDictKeys_NewWithValues, dictkeys_deallocFree list for PyDictKeysObject / PyDictValues blocks.objects/freelist.go:DictKeysAlloc
200-230_PyFrame_Alloc, _PyFrame_ClearLocals contributionFrame object free list.objects/freelist.go:FrameAlloc
230-251_Py_ClearFreeListsDrain all free lists; called from GC, _Py_ClearStandardStreamEncoding, and interpreter teardown.objects/freelist.go:ClearFreeLists

Reading

Free list structure (lines 1 to 80)

cpython 3.14 @ ab2d84fe1023/Python/freelist.c#L1-80

/* pycore_freelist.h */
typedef struct _Py_freelist {
PyObject *items[_PyFreeList_MAXFREELIST];
int numfree;
} _Py_freelist;

typedef struct _PyFreeListState {
_Py_freelist floats;
_Py_freelist tuples[PyTuple_MAXSAVESIZE];
_Py_freelist lists;
_Py_freelist dict_keys;
_Py_freelist frames;
} _PyFreeListState;

Each _Py_freelist is a fixed-capacity array of pointers and a count. The array size _PyFreeList_MAXFREELIST defaults to 256 for floats and 80 for most others; it is a compile-time constant so bounds checking is a single comparison rather than a heap query. Pointers stored in the array are type-erased PyObject *; the next-pointer for the linked list is physically the object's ob_type field (which is otherwise unused for a dead object). This trick avoids adding a next pointer to every object header while keeping the free list threaded without additional allocation.

The _Py_freelists_GET() inline resolves to &tstate->interp->freelist_state, so all free list operations are pure pointer arithmetic with no locking and no function call overhead after inlining.

_PyFloat_FromDouble fast path (lines 40 to 80)

cpython 3.14 @ ab2d84fe1023/Python/freelist.c#L40-80

PyObject *
_PyFloat_FromDouble(PyInterpreterState *interp, double fval)
{
_PyFreeListState *state = _Py_freelists_GET();
PyFloatObject *op = (PyFloatObject *)state->floats.items[--state->floats.numfree];
if (state->floats.numfree < 0) {
/* list was empty */
state->floats.numfree = 0;
op = PyObject_Malloc(sizeof(PyFloatObject));
if (op == NULL) {
return PyErr_NoMemory();
}
}
_PyObject_Init((PyObject *)op, &PyFloat_Type);
op->ob_fval = fval;
return (PyObject *)op;
}

The allocator pre-decrements numfree and reads from the slot. If numfree goes negative the list was empty; the index is reset to zero and PyObject_Malloc is called instead. On the fast path no branch is taken after the pre-decrement: the object is already in cache, and only _PyObject_Init and the ob_fval store remain. The complementary dealloc in float_dealloc checks whether numfree < _PyFreeList_MAXFREELIST before pushing:

static void
float_dealloc(PyObject *op)
{
_PyFreeListState *state = _Py_freelists_GET();
if (state->floats.numfree < _PyFreeList_MAXFREELIST) {
state->floats.items[state->floats.numfree++] = op;
return;
}
PyObject_Free(op);
}

A float that would overflow the cache goes directly to PyObject_Free. The cap keeps memory bounded; in practice benchmarks show that 256 slots cover the steady-state working set of all common numerical workloads.

_Py_ClearFreeLists (lines 230 to 251)

cpython 3.14 @ ab2d84fe1023/Python/freelist.c#L230-251

void
_Py_ClearFreeLists(_PyFreeListState *state, int is_finalization)
{
/* floats */
while (state->floats.numfree > 0) {
PyFloatObject *op = (PyFloatObject *)
state->floats.items[--state->floats.numfree];
PyObject_Free(op);
}
/* tuples (per size 0..PyTuple_MAXSAVESIZE-1) */
for (int i = 0; i < PyTuple_MAXSAVESIZE; i++) {
while (state->tuples[i].numfree > 0) {
PyTupleObject *op = (PyTupleObject *)
state->tuples[i].items[--state->tuples[i].numfree];
PyObject_GC_Del(op);
}
}
/* lists, dict_keys, frames ... */
_PyList_ClearFreeList(state, is_finalization);
_PyDict_ClearFreeList(state, is_finalization);
_PyFrame_ClearFreeList(state, is_finalization);
}

_Py_ClearFreeLists is called in three situations: by the cyclic garbage collector after a full collection (is_finalization = 0), by _Py_Finalize during interpreter shutdown (is_finalization = 1), and by _testinternalcapi.get_memory_usage in the test suite. For floats and tuples the drain loop is open-coded here because those types use a pure array structure. Lists, dict keys, and frames delegate to their own ClearFreeList helpers because those types have additional bookkeeping (for example, the list header free list is separate from the item-array allocation, and frames carry a separate GC link).

When is_finalization is set, _PyList_ClearFreeList and friends also clear any interned state that would create reference cycles, ensuring a clean shutdown even if the interpreter is re-initialized in the same process.

gopy mirror

objects/freelist.go implements the same structure using Go's sync.Pool for the float and frame lists, since Go's GC moves objects and raw pointer threading through ob_type is not possible. For tuple and list headers, which have known fixed sizes, a channel-based pool of capacity 256 is used instead of sync.Pool to avoid the per-GC-cycle clearing that sync.Pool performs (which would undermine the performance goal).

ClearFreeLists in Go is called from the gc module's collect() function and from interpreter teardown. Because Go's GC reclaims memory independently, clearing the pools is only necessary for the semantic guarantee (that memory is returned before the next collection counts objects) rather than for actual memory pressure.