From f05fefc8e095f3a45a79a045794fe2b6f43824be Mon Sep 17 00:00:00 2001 From: Brij <97006829+brijkapadia@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:37:12 -0400 Subject: [PATCH 1/4] gh-135832: Implement `_Py_DECREF_SPECIALIZED` and `PyStackRef_CLOSE_SPECIALIZED` for free-threading This should help single-threaded performance. --- Include/internal/pycore_object.h | 20 +++++++++++++++++--- Include/refcount.h | 1 + Objects/object.c | 24 ++++++++++++++++++++++++ Python/ceval.h | 21 ++++++++++++++++++++- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 41786cb267c2e96..f70ab123eebb529 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -253,11 +253,25 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) } #else -// TODO: implement Py_DECREF specializations for Py_GIL_DISABLED build static inline void _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) { - Py_DECREF(op); + uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); + if (local == _Py_IMMORTAL_REFCNT_LOCAL) { + _Py_DECREF_IMMORTAL_STAT_INC(); + return; + } + _Py_DECREF_STAT_INC(); + if (_Py_IsOwnedByCurrentThread(op)) { + local--; + _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local); + if (local == 0) { + _Py_MergeZeroLocalRefcountSpecialized(op, destruct); + } + } + else { + _Py_DecRefShared(op); + } } static inline int @@ -465,7 +479,7 @@ static inline void Py_DECREF_MORTAL_SPECIALIZED(PyObject *op, destructor destruc #endif #else // Py_GIL_DISABLED # define Py_DECREF_MORTAL(op) Py_DECREF(op) -# define Py_DECREF_MORTAL_SPECIALIZED(op, destruct) Py_DECREF(op) +# define Py_DECREF_MORTAL_SPECIALIZED(op, destruct) _Py_DECREF_SPECIALIZED(op, destruct) #endif /* Inline functions trading binary compatibility for speed: diff --git a/Include/refcount.h b/Include/refcount.h index 80fe7ff70a11e87..43ddd86801277b5 100644 --- a/Include/refcount.h +++ b/Include/refcount.h @@ -321,6 +321,7 @@ PyAPI_FUNC(void) _Py_DecRefSharedDebug(PyObject *, const char *, int); // zero. Otherwise, the thread gives up ownership and merges the reference // count fields. PyAPI_FUNC(void) _Py_MergeZeroLocalRefcount(PyObject *); +PyAPI_FUNC(void) _Py_MergeZeroLocalRefcountSpecialized(PyObject *, const destructor); #endif // Py_GIL_DISABLED #endif // Py_LIMITED_API diff --git a/Objects/object.c b/Objects/object.c index fadd9273a36607c..d726e61f0f4eec3 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -464,6 +464,30 @@ _Py_MergeZeroLocalRefcount(PyObject *op) } } +void +_Py_MergeZeroLocalRefcountSpecialized(PyObject *op, const destructor destruct) +{ + assert(op->ob_ref_local == 0); + + Py_ssize_t shared = _Py_atomic_load_ssize_acquire(&op->ob_ref_shared); + if (shared == 0) { + destruct(op); + return; + } + + _Py_atomic_store_uintptr_relaxed(&op->ob_tid, 0); + + Py_ssize_t new_shared; + do { + new_shared = (shared & ~_Py_REF_SHARED_FLAG_MASK) | _Py_REF_MERGED; + } while (!_Py_atomic_compare_exchange_ssize(&op->ob_ref_shared, + &shared, new_shared)); + + if (new_shared == _Py_REF_MERGED) { + destruct(op); + } +} + Py_ssize_t _Py_ExplicitMergeRefcount(PyObject *op, Py_ssize_t extra) { diff --git a/Python/ceval.h b/Python/ceval.h index 0437ab85c5a6682..4ede287d9e5e271 100644 --- a/Python/ceval.h +++ b/Python/ceval.h @@ -132,7 +132,26 @@ } while (0) #undef _Py_DECREF_SPECIALIZED -#define _Py_DECREF_SPECIALIZED(arg, dealloc) Py_DECREF(arg) +#define _Py_DECREF_SPECIALIZED(arg, dealloc) \ + do { \ + PyObject *op = _PyObject_CAST(arg); \ + uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); \ + if (local == _Py_IMMORTAL_REFCNT_LOCAL) { \ + _Py_DECREF_IMMORTAL_STAT_INC(); \ + break; \ + } \ + _Py_DECREF_STAT_INC(); \ + if (_Py_IsOwnedByCurrentThread(op)) { \ + local--; \ + _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local); \ + if (local == 0) { \ + _Py_MergeZeroLocalRefcountSpecialized(op, dealloc); \ + } \ + } \ + else { \ + _Py_DecRefShared(op); \ + } \ + } while (0) #endif #endif From 81c24303b2548ca0066aa7e4f40da4e67add2f53 Mon Sep 17 00:00:00 2001 From: Brij <97006829+brijkapadia@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:35:51 -0400 Subject: [PATCH 2/4] fix refleaks --- Include/internal/pycore_object.h | 3 +++ Objects/object.c | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index f70ab123eebb529..5f493a2cd3f7579 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -262,6 +262,9 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) return; } _Py_DECREF_STAT_INC(); +#ifdef Py_REF_DEBUG + _Py_DEC_REFTOTAL(PyInterpreterState_Get()); +#endif if (_Py_IsOwnedByCurrentThread(op)) { local--; _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local); diff --git a/Objects/object.c b/Objects/object.c index d726e61f0f4eec3..3524199ec191b95 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -471,6 +471,10 @@ _Py_MergeZeroLocalRefcountSpecialized(PyObject *op, const destructor destruct) Py_ssize_t shared = _Py_atomic_load_ssize_acquire(&op->ob_ref_shared); if (shared == 0) { +#ifdef Py_TRACE_REFS + _Py_ForgetReference(op); +#endif + _PyReftracerTrack(op, PyRefTracer_DESTROY); destruct(op); return; } @@ -484,6 +488,10 @@ _Py_MergeZeroLocalRefcountSpecialized(PyObject *op, const destructor destruct) &shared, new_shared)); if (new_shared == _Py_REF_MERGED) { +#ifdef Py_TRACE_REFS + _Py_ForgetReference(op); +#endif + _PyReftracerTrack(op, PyRefTracer_DESTROY); destruct(op); } } From 176f6a81c325b4a5d7bb681ba0990569e4d5f248 Mon Sep 17 00:00:00 2001 From: Brij <97006829+brijkapadia@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:43:19 -0400 Subject: [PATCH 3/4] fix race --- Include/internal/pycore_object.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 5f493a2cd3f7579..f70ab123eebb529 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -262,9 +262,6 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) return; } _Py_DECREF_STAT_INC(); -#ifdef Py_REF_DEBUG - _Py_DEC_REFTOTAL(PyInterpreterState_Get()); -#endif if (_Py_IsOwnedByCurrentThread(op)) { local--; _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local); From 3e203b4dfcc53ab1137dd1071c5ea82020e34901 Mon Sep 17 00:00:00 2001 From: Brij <97006829+brijkapadia@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:50:51 -0400 Subject: [PATCH 4/4] fix tests --- Include/internal/pycore_object.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index f70ab123eebb529..e2aab29698f6c0d 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -256,6 +256,9 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) static inline void _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) { +#ifdef Py_REF_DEBUG + Py_DECREF(op); +#else uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); if (local == _Py_IMMORTAL_REFCNT_LOCAL) { _Py_DECREF_IMMORTAL_STAT_INC(); @@ -272,6 +275,7 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) else { _Py_DecRefShared(op); } +#endif } static inline int