[3.14] gh-145779: Improve classmethod/staticmethod scaling in free-threaded build (gh-145826) (#146088)

Add special cases for classmethod and staticmethod descriptors in
_PyObject_GetMethodStackRef() to avoid calling tp_descr_get, which
avoids reference count contention on the bound method and underlying
callable. This improves scaling when calling classmethods and
staticmethods from multiple threads.

Also refactor method_vectorcall in classobject.c into a new
_PyObject_VectorcallPrepend() helper so that it can be used by
PyObject_VectorcallMethod as well.

(cherry picked from commit e0f7c1097e)
This commit is contained in:
Sam Gross 2026-03-19 10:49:12 -04:00 committed by GitHub
parent 7f29c1d0da
commit fa3143a1d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 387 additions and 140 deletions

View file

@ -2295,39 +2295,19 @@ dummy_func(
op(_LOAD_ATTR, (owner -- attr, self_or_null[oparg&1])) {
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
PyObject *attr_o;
if (oparg & 1) {
/* Designed to work in tandem with CALL, pushes two values. */
attr_o = NULL;
int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
if (is_meth) {
/* We can bypass temporary bound method object.
meth is unbound method and obj is self.
meth | self | arg1 | ... | argN
*/
assert(attr_o != NULL); // No errors on this branch
self_or_null[0] = owner; // Transfer ownership
DEAD(owner);
}
else {
/* meth is not an unbound method (but a regular attr, or
something was returned by a descriptor protocol). Set
the second element of the stack to NULL, to signal
CALL that it's not a method call.
meth | NULL | arg1 | ... | argN
*/
PyStackRef_CLOSE(owner);
ERROR_IF(attr_o == NULL);
self_or_null[0] = PyStackRef_NULL;
}
attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name, self_or_null);
DEAD(owner);
ERROR_IF(PyStackRef_IsNull(attr));
}
else {
/* Classic, pushes one value. */
attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
PyStackRef_CLOSE(owner);
ERROR_IF(attr_o == NULL);
attr = PyStackRef_FromPyObjectSteal(attr_o);
}
attr = PyStackRef_FromPyObjectSteal(attr_o);
}
macro(LOAD_ATTR) =