Skip to content

Skip fetching the thread state for non-GC-tracked objects in _Py_Dealloc #154429

Description

@johng

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions