Feature or enhancement
Proposal:
_Py_Dealloc() unconditionally fetches the current thread state and computes the C recursion margin on every deallocation:
PyThreadState *tstate = _PyThreadState_GET();
intptr_t margin = _Py_RecursionLimit_GetMargin(tstate);
if (margin < 2 && gc_flag) { /* trashcan deposit */ }
Both values are only consumed by the trashcan mechanism, which applies exclusively to GC-tracked types — only they can form the deep deallocation chains the trashcan guards against. For the common non-GC case (int, float, str and other atomic types), fetching the thread state and computing the margin is wasted work.
On most platforms _PyThreadState_GET() is a cheap thread-local load, but on macOS a _Thread_local access compiles to a function call to the TLV resolver (tlv_get_addr), so this is a real per-deallocation cost on Apple Silicon. Deallocation of non-GC atomic temporaries is one of the most frequent operations in typical workloads.
Gating the thread-state fetch and margin computation behind gc_flag skips them entirely for non-GC objects. The change is small and semantics-preserving.
Preliminary microbenchmark results (compute/allocation-heavy microbenchmarks, non-PGO build): ~1% faster on an Apple Silicon free-threaded build; neutral on Linux/x86-64 (where the thread-local read is already a fixed-offset load, so there is nothing to skip). Broader benchmarking (pyperformance, PGO/LTO) is still to be done.
Has this already been discussed elsewhere?
This is a minor feature or performance improvement
Linked PRs
Feature or enhancement
Proposal:
_Py_Dealloc()unconditionally fetches the current thread state and computes the C recursion margin on every deallocation:Both values are only consumed by the trashcan mechanism, which applies exclusively to GC-tracked types — only they can form the deep deallocation chains the trashcan guards against. For the common non-GC case (
int,float,strand other atomic types), fetching the thread state and computing the margin is wasted work.On most platforms
_PyThreadState_GET()is a cheap thread-local load, but on macOS a_Thread_localaccess compiles to a function call to the TLV resolver (tlv_get_addr), so this is a real per-deallocation cost on Apple Silicon. Deallocation of non-GC atomic temporaries is one of the most frequent operations in typical workloads.Gating the thread-state fetch and margin computation behind
gc_flagskips them entirely for non-GC objects. The change is small and semantics-preserving.Preliminary microbenchmark results (compute/allocation-heavy microbenchmarks, non-PGO build): ~1% faster on an Apple Silicon free-threaded build; neutral on Linux/x86-64 (where the thread-local read is already a fixed-offset load, so there is nothing to skip). Broader benchmarking (pyperformance, PGO/LTO) is still to be done.
Has this already been discussed elsewhere?
This is a minor feature or performance improvement
Linked PRs
_Py_Dealloc#154430