mirror of
https://github.com/python/cpython.git
synced 2026-04-15 00:00:57 +00:00
[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:
parent
7f29c1d0da
commit
fa3143a1d2
15 changed files with 387 additions and 140 deletions
|
|
@ -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) =
|
||||
|
|
|
|||
|
|
@ -736,15 +736,19 @@ _PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys)
|
|||
PyObject *seen = NULL;
|
||||
PyObject *dummy = NULL;
|
||||
PyObject *values = NULL;
|
||||
PyObject *get = NULL;
|
||||
// We use the two argument form of map.get(key, default) for two reasons:
|
||||
// - Atomically check for a key and get its value without error handling.
|
||||
// - Don't cause key creation or resizing in dict subclasses like
|
||||
// collections.defaultdict that define __missing__ (or similar).
|
||||
int meth_found = _PyObject_GetMethod(map, &_Py_ID(get), &get);
|
||||
if (get == NULL) {
|
||||
_PyCStackRef self, method;
|
||||
_PyThreadState_PushCStackRef(tstate, &self);
|
||||
_PyThreadState_PushCStackRef(tstate, &method);
|
||||
self.ref = PyStackRef_FromPyObjectBorrow(map);
|
||||
int res = _PyObject_GetMethodStackRef(tstate, &self.ref, &_Py_ID(get), &method.ref);
|
||||
if (res < 0) {
|
||||
goto fail;
|
||||
}
|
||||
PyObject *get = PyStackRef_AsPyObjectBorrow(method.ref);
|
||||
seen = PySet_New(NULL);
|
||||
if (seen == NULL) {
|
||||
goto fail;
|
||||
|
|
@ -768,9 +772,10 @@ _PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys)
|
|||
}
|
||||
goto fail;
|
||||
}
|
||||
PyObject *args[] = { map, key, dummy };
|
||||
PyObject *self_obj = PyStackRef_AsPyObjectBorrow(self.ref);
|
||||
PyObject *args[] = { self_obj, key, dummy };
|
||||
PyObject *value = NULL;
|
||||
if (meth_found) {
|
||||
if (!PyStackRef_IsNull(self.ref)) {
|
||||
value = PyObject_Vectorcall(get, args, 3, NULL);
|
||||
}
|
||||
else {
|
||||
|
|
@ -791,12 +796,14 @@ _PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys)
|
|||
}
|
||||
// Success:
|
||||
done:
|
||||
Py_DECREF(get);
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
_PyThreadState_PopCStackRef(tstate, &self);
|
||||
Py_DECREF(seen);
|
||||
Py_DECREF(dummy);
|
||||
return values;
|
||||
fail:
|
||||
Py_XDECREF(get);
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
_PyThreadState_PopCStackRef(tstate, &self);
|
||||
Py_XDECREF(seen);
|
||||
Py_XDECREF(dummy);
|
||||
Py_XDECREF(values);
|
||||
|
|
@ -997,6 +1004,26 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
|
|||
|
||||
#include "ceval_macros.h"
|
||||
|
||||
|
||||
_PyStackRef
|
||||
_Py_LoadAttr_StackRefSteal(
|
||||
PyThreadState *tstate, _PyStackRef owner,
|
||||
PyObject *name, _PyStackRef *self_or_null)
|
||||
{
|
||||
// Use _PyCStackRefs to ensure that both method and self are visible to
|
||||
// the GC. Even though self_or_null is on the evaluation stack, it may be
|
||||
// after the stackpointer and therefore not visible to the GC.
|
||||
_PyCStackRef method, self;
|
||||
_PyThreadState_PushCStackRef(tstate, &method);
|
||||
_PyThreadState_PushCStackRef(tstate, &self);
|
||||
self.ref = owner; // steal reference to owner
|
||||
// NOTE: method.ref is initialized to PyStackRef_NULL and remains null on
|
||||
// error, so we don't need to explicitly use the return code from the call.
|
||||
_PyObject_GetMethodStackRef(tstate, &self.ref, name, &method.ref);
|
||||
*self_or_null = _PyThreadState_PopCStackRefSteal(tstate, &self);
|
||||
return _PyThreadState_PopCStackRefSteal(tstate, &method);
|
||||
}
|
||||
|
||||
int _Py_CheckRecursiveCallPy(
|
||||
PyThreadState *tstate)
|
||||
{
|
||||
|
|
|
|||
26
Python/executor_cases.c.h
generated
26
Python/executor_cases.c.h
generated
|
|
@ -3189,32 +3189,20 @@
|
|||
owner = stack_pointer[-1];
|
||||
self_or_null = &stack_pointer[0];
|
||||
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
|
||||
PyObject *attr_o;
|
||||
if (oparg & 1) {
|
||||
attr_o = NULL;
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
|
||||
attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name, self_or_null);
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame);
|
||||
if (is_meth) {
|
||||
assert(attr_o != NULL);
|
||||
self_or_null[0] = owner;
|
||||
}
|
||||
else {
|
||||
stack_pointer += -1;
|
||||
if (PyStackRef_IsNull(attr)) {
|
||||
stack_pointer[-1] = attr;
|
||||
stack_pointer += (oparg&1);
|
||||
assert(WITHIN_STACK_BOUNDS());
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
PyStackRef_CLOSE(owner);
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame);
|
||||
if (attr_o == NULL) {
|
||||
JUMP_TO_ERROR();
|
||||
}
|
||||
self_or_null[0] = PyStackRef_NULL;
|
||||
stack_pointer += 1;
|
||||
JUMP_TO_ERROR();
|
||||
}
|
||||
}
|
||||
else {
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
|
||||
PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame);
|
||||
stack_pointer += -1;
|
||||
assert(WITHIN_STACK_BOUNDS());
|
||||
|
|
@ -3224,9 +3212,9 @@
|
|||
if (attr_o == NULL) {
|
||||
JUMP_TO_ERROR();
|
||||
}
|
||||
attr = PyStackRef_FromPyObjectSteal(attr_o);
|
||||
stack_pointer += 1;
|
||||
}
|
||||
attr = PyStackRef_FromPyObjectSteal(attr_o);
|
||||
stack_pointer[-1] = attr;
|
||||
stack_pointer += (oparg&1);
|
||||
assert(WITHIN_STACK_BOUNDS());
|
||||
|
|
|
|||
25
Python/generated_cases.c.h
generated
25
Python/generated_cases.c.h
generated
|
|
@ -8014,32 +8014,17 @@
|
|||
{
|
||||
self_or_null = &stack_pointer[0];
|
||||
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
|
||||
PyObject *attr_o;
|
||||
if (oparg & 1) {
|
||||
attr_o = NULL;
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
|
||||
attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name, self_or_null);
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame);
|
||||
if (is_meth) {
|
||||
assert(attr_o != NULL);
|
||||
self_or_null[0] = owner;
|
||||
}
|
||||
else {
|
||||
stack_pointer += -1;
|
||||
assert(WITHIN_STACK_BOUNDS());
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
PyStackRef_CLOSE(owner);
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame);
|
||||
if (attr_o == NULL) {
|
||||
JUMP_TO_LABEL(error);
|
||||
}
|
||||
self_or_null[0] = PyStackRef_NULL;
|
||||
stack_pointer += 1;
|
||||
if (PyStackRef_IsNull(attr)) {
|
||||
JUMP_TO_LABEL(pop_1_error);
|
||||
}
|
||||
}
|
||||
else {
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
|
||||
PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame);
|
||||
stack_pointer += -1;
|
||||
assert(WITHIN_STACK_BOUNDS());
|
||||
|
|
@ -8049,9 +8034,9 @@
|
|||
if (attr_o == NULL) {
|
||||
JUMP_TO_LABEL(error);
|
||||
}
|
||||
attr = PyStackRef_FromPyObjectSteal(attr_o);
|
||||
stack_pointer += 1;
|
||||
}
|
||||
attr = PyStackRef_FromPyObjectSteal(attr_o);
|
||||
}
|
||||
stack_pointer[-1] = attr;
|
||||
stack_pointer += (oparg&1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue