gh-131586: Avoid refcount contention in some "special" calls (#131588)

In the free threaded build, the `_PyObject_LookupSpecial()` call can lead to
reference count contention on the returned function object becuase it
doesn't use stackrefs. Refactor some of the callers to use
`_PyObject_MaybeCallSpecialNoArgs`, which uses stackrefs internally.

This fixes the scaling bottleneck in the "lookup_special" microbenchmark
in `ftscalingbench.py`. However, the are still some uses of
`_PyObject_LookupSpecial()` that need to be addressed in future PRs.
This commit is contained in:
Sam Gross 2025-03-26 14:38:47 -04:00 committed by GitHub
parent 3d4ac1a2c2
commit 67fbfb42bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 450 additions and 374 deletions

View file

@ -592,7 +592,7 @@ PyStackRef_XCLOSE(_PyStackRef ref)
// Note: this is a macro because MSVC (Windows) has trouble inlining it.
#define PyStackRef_Is(a, b) (((a).bits & (~Py_TAG_REFCNT)) == ((b).bits & (~Py_TAG_REFCNT)))
#define PyStackRef_Is(a, b) (((a).bits & (~Py_TAG_BITS)) == ((b).bits & (~Py_TAG_BITS)))
#endif // !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
@ -640,6 +640,28 @@ PyStackRef_FunctionCheck(_PyStackRef stackref)
return PyFunction_Check(PyStackRef_AsPyObjectBorrow(stackref));
}
static inline void
_PyThreadState_PushCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
{
#ifdef Py_GIL_DISABLED
_PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
ref->next = tstate_impl->c_stack_refs;
tstate_impl->c_stack_refs = ref;
#endif
ref->ref = PyStackRef_NULL;
}
static inline void
_PyThreadState_PopCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
{
#ifdef Py_GIL_DISABLED
_PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
assert(tstate_impl->c_stack_refs == ref);
tstate_impl->c_stack_refs = ref->next;
#endif
PyStackRef_XCLOSE(ref->ref);
}
#ifdef Py_GIL_DISABLED
static inline int
@ -656,6 +678,17 @@ _Py_TryIncrefCompareStackRef(PyObject **src, PyObject *op, _PyStackRef *out)
return 0;
}
static inline int
_Py_TryXGetStackRef(PyObject **src, _PyStackRef *out)
{
PyObject *op = _Py_atomic_load_ptr_relaxed(src);
if (op == NULL) {
*out = PyStackRef_NULL;
return 1;
}
return _Py_TryIncrefCompareStackRef(src, op, out);
}
#endif
// Like Py_VISIT but for _PyStackRef fields