mirror of
https://github.com/python/cpython.git
synced 2026-04-14 15:50:50 +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
|
|
@ -98,6 +98,14 @@ _PyObject_CallMethodIdOneArg(PyObject *self, _Py_Identifier *name, PyObject *arg
|
|||
}
|
||||
|
||||
|
||||
extern PyObject *_PyObject_VectorcallPrepend(
|
||||
PyThreadState *tstate,
|
||||
PyObject *callable,
|
||||
PyObject *arg,
|
||||
PyObject *const *args,
|
||||
size_t nargsf,
|
||||
PyObject *kwnames);
|
||||
|
||||
/* === Vectorcall protocol (PEP 590) ============================= */
|
||||
|
||||
// Call callable using tp_call. Arguments are like PyObject_Vectorcall(),
|
||||
|
|
|
|||
|
|
@ -383,6 +383,11 @@ extern int _PyRunRemoteDebugger(PyThreadState *tstate);
|
|||
#define SPECIAL___AEXIT__ 3
|
||||
#define SPECIAL_MAX 3
|
||||
|
||||
PyAPI_FUNC(_PyStackRef)
|
||||
_Py_LoadAttr_StackRefSteal(
|
||||
PyThreadState *tstate, _PyStackRef owner,
|
||||
PyObject *name, _PyStackRef *self_or_null);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -47,6 +47,17 @@ static inline PyObject* _PyFunction_GET_BUILTINS(PyObject *func) {
|
|||
#define _PyFunction_GET_BUILTINS(func) _PyFunction_GET_BUILTINS(_PyObject_CAST(func))
|
||||
|
||||
|
||||
/* Get the callable wrapped by a classmethod.
|
||||
Returns a borrowed reference.
|
||||
The caller must ensure 'cm' is a classmethod object. */
|
||||
extern PyObject *_PyClassMethod_GetFunc(PyObject *cm);
|
||||
|
||||
/* Get the callable wrapped by a staticmethod.
|
||||
Returns a borrowed reference.
|
||||
The caller must ensure 'sm' is a staticmethod object. */
|
||||
extern PyObject *_PyStaticMethod_GetFunc(PyObject *sm);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -897,6 +897,9 @@ extern PyObject *_PyType_LookupRefAndVersion(PyTypeObject *, PyObject *,
|
|||
extern unsigned int
|
||||
_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out);
|
||||
|
||||
extern int _PyObject_GetMethodStackRef(PyThreadState *ts, _PyStackRef *self,
|
||||
PyObject *name, _PyStackRef *method);
|
||||
|
||||
// Cache the provided init method in the specialization cache of type if the
|
||||
// provided type version matches the current version of the type.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -127,6 +127,13 @@ _PyStackRef_FromPyObjectSteal(PyObject *obj, const char *filename, int linenumbe
|
|||
}
|
||||
#define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj), __FILE__, __LINE__)
|
||||
|
||||
static inline _PyStackRef
|
||||
_PyStackRef_FromPyObjectBorrow(PyObject *obj, const char *filename, int linenumber)
|
||||
{
|
||||
return _Py_stackref_create(obj, filename, linenumber);
|
||||
}
|
||||
#define PyStackRef_FromPyObjectBorrow(obj) _PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj), __FILE__, __LINE__)
|
||||
|
||||
static inline _PyStackRef
|
||||
_PyStackRef_FromPyObjectImmortal(PyObject *obj, const char *filename, int linenumber)
|
||||
{
|
||||
|
|
@ -320,6 +327,14 @@ _PyStackRef_FromPyObjectSteal(PyObject *obj)
|
|||
}
|
||||
# define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj))
|
||||
|
||||
static inline _PyStackRef
|
||||
PyStackRef_FromPyObjectBorrow(PyObject *obj)
|
||||
{
|
||||
assert(obj != NULL);
|
||||
assert(((uintptr_t)obj & Py_TAG_BITS) == 0);
|
||||
return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED };
|
||||
}
|
||||
|
||||
static inline bool
|
||||
PyStackRef_IsHeapSafe(_PyStackRef stackref)
|
||||
{
|
||||
|
|
@ -538,6 +553,13 @@ PyStackRef_FromPyObjectSteal(PyObject *obj)
|
|||
return ref;
|
||||
}
|
||||
|
||||
static inline _PyStackRef
|
||||
PyStackRef_FromPyObjectBorrow(PyObject *obj)
|
||||
{
|
||||
assert(obj != NULL);
|
||||
return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT };
|
||||
}
|
||||
|
||||
static inline _PyStackRef
|
||||
PyStackRef_FromPyObjectStealMortal(PyObject *obj)
|
||||
{
|
||||
|
|
@ -753,6 +775,17 @@ _PyThreadState_PopCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
|
|||
PyStackRef_XCLOSE(ref->ref);
|
||||
}
|
||||
|
||||
static inline _PyStackRef
|
||||
_PyThreadState_PopCStackRefSteal(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
|
||||
return ref->ref;
|
||||
}
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
|
||||
static inline int
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Improve scaling of :func:`classmethod` and :func:`staticmethod` calls in
|
||||
the free-threaded build by avoiding the descriptor ``__get__`` call.
|
||||
119
Objects/call.c
119
Objects/call.c
|
|
@ -825,6 +825,60 @@ object_vacall(PyThreadState *tstate, PyObject *base,
|
|||
return result;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
_PyObject_VectorcallPrepend(PyThreadState *tstate, PyObject *callable,
|
||||
PyObject *arg, PyObject *const *args,
|
||||
size_t nargsf, PyObject *kwnames)
|
||||
{
|
||||
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
|
||||
assert(nargs == 0 || args[nargs-1]);
|
||||
|
||||
PyObject *result;
|
||||
if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) {
|
||||
/* PY_VECTORCALL_ARGUMENTS_OFFSET is set, so we are allowed to mutate the vector */
|
||||
PyObject **newargs = (PyObject**)args - 1;
|
||||
nargs += 1;
|
||||
PyObject *tmp = newargs[0];
|
||||
newargs[0] = arg;
|
||||
assert(newargs[nargs-1]);
|
||||
result = _PyObject_VectorcallTstate(tstate, callable, newargs,
|
||||
nargs, kwnames);
|
||||
newargs[0] = tmp;
|
||||
}
|
||||
else {
|
||||
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
|
||||
Py_ssize_t totalargs = nargs + nkwargs;
|
||||
if (totalargs == 0) {
|
||||
return _PyObject_VectorcallTstate(tstate, callable, &arg, 1, NULL);
|
||||
}
|
||||
|
||||
PyObject *newargs_stack[_PY_FASTCALL_SMALL_STACK];
|
||||
PyObject **newargs;
|
||||
if (totalargs <= (Py_ssize_t)Py_ARRAY_LENGTH(newargs_stack) - 1) {
|
||||
newargs = newargs_stack;
|
||||
}
|
||||
else {
|
||||
newargs = PyMem_Malloc((totalargs+1) * sizeof(PyObject *));
|
||||
if (newargs == NULL) {
|
||||
_PyErr_NoMemory(tstate);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
/* use borrowed references */
|
||||
newargs[0] = arg;
|
||||
/* bpo-37138: since totalargs > 0, it's impossible that args is NULL.
|
||||
* We need this, since calling memcpy() with a NULL pointer is
|
||||
* undefined behaviour. */
|
||||
assert(args != NULL);
|
||||
memcpy(newargs + 1, args, totalargs * sizeof(PyObject *));
|
||||
result = _PyObject_VectorcallTstate(tstate, callable,
|
||||
newargs, nargs+1, kwnames);
|
||||
if (newargs != newargs_stack) {
|
||||
PyMem_Free(newargs);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
|
||||
|
|
@ -835,28 +889,44 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
|
|||
assert(PyVectorcall_NARGS(nargsf) >= 1);
|
||||
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
PyObject *callable = NULL;
|
||||
_PyCStackRef self, method;
|
||||
_PyThreadState_PushCStackRef(tstate, &self);
|
||||
_PyThreadState_PushCStackRef(tstate, &method);
|
||||
/* Use args[0] as "self" argument */
|
||||
int unbound = _PyObject_GetMethod(args[0], name, &callable);
|
||||
if (callable == NULL) {
|
||||
self.ref = PyStackRef_FromPyObjectBorrow(args[0]);
|
||||
int unbound = _PyObject_GetMethodStackRef(tstate, &self.ref, name, &method.ref);
|
||||
if (unbound < 0) {
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
_PyThreadState_PopCStackRef(tstate, &self);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (unbound) {
|
||||
/* We must remove PY_VECTORCALL_ARGUMENTS_OFFSET since
|
||||
* that would be interpreted as allowing to change args[-1] */
|
||||
nargsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET;
|
||||
}
|
||||
else {
|
||||
PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
|
||||
PyObject *self_obj = PyStackRef_AsPyObjectBorrow(self.ref);
|
||||
PyObject *result;
|
||||
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
|
||||
if (self_obj == NULL) {
|
||||
/* Skip "self". We can keep PY_VECTORCALL_ARGUMENTS_OFFSET since
|
||||
* args[-1] in the onward call is args[0] here. */
|
||||
args++;
|
||||
nargsf--;
|
||||
result = _PyObject_VectorcallTstate(tstate, callable,
|
||||
args + 1, nargsf - 1, kwnames);
|
||||
}
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
|
||||
PyObject *result = _PyObject_VectorcallTstate(tstate, callable,
|
||||
args, nargsf, kwnames);
|
||||
Py_DECREF(callable);
|
||||
else if (self_obj == args[0]) {
|
||||
/* We must remove PY_VECTORCALL_ARGUMENTS_OFFSET since
|
||||
* that would be interpreted as allowing to change args[-1] */
|
||||
result = _PyObject_VectorcallTstate(tstate, callable, args,
|
||||
nargsf & ~PY_VECTORCALL_ARGUMENTS_OFFSET,
|
||||
kwnames);
|
||||
}
|
||||
else {
|
||||
/* classmethod: self_obj is the type, not args[0]. Replace
|
||||
* args[0] with self_obj and call the underlying callable. */
|
||||
result = _PyObject_VectorcallPrepend(tstate, callable, self_obj,
|
||||
args + 1, nargsf - 1, kwnames);
|
||||
}
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
_PyThreadState_PopCStackRef(tstate, &self);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -869,19 +939,26 @@ PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
|
|||
return null_error(tstate);
|
||||
}
|
||||
|
||||
PyObject *callable = NULL;
|
||||
int is_method = _PyObject_GetMethod(obj, name, &callable);
|
||||
if (callable == NULL) {
|
||||
_PyCStackRef self, method;
|
||||
_PyThreadState_PushCStackRef(tstate, &self);
|
||||
_PyThreadState_PushCStackRef(tstate, &method);
|
||||
self.ref = PyStackRef_FromPyObjectBorrow(obj);
|
||||
int res = _PyObject_GetMethodStackRef(tstate, &self.ref, name, &method.ref);
|
||||
if (res < 0) {
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
_PyThreadState_PopCStackRef(tstate, &self);
|
||||
return NULL;
|
||||
}
|
||||
obj = is_method ? obj : NULL;
|
||||
PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
|
||||
PyObject *self_obj = PyStackRef_AsPyObjectBorrow(self.ref);
|
||||
|
||||
va_list vargs;
|
||||
va_start(vargs, name);
|
||||
PyObject *result = object_vacall(tstate, obj, callable, vargs);
|
||||
PyObject *result = object_vacall(tstate, self_obj, callable, vargs);
|
||||
va_end(vargs);
|
||||
|
||||
Py_DECREF(callable);
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
_PyThreadState_PopCStackRef(tstate, &self);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,54 +51,7 @@ method_vectorcall(PyObject *method, PyObject *const *args,
|
|||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
PyObject *self = PyMethod_GET_SELF(method);
|
||||
PyObject *func = PyMethod_GET_FUNCTION(method);
|
||||
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
|
||||
assert(nargs == 0 || args[nargs-1]);
|
||||
|
||||
PyObject *result;
|
||||
if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) {
|
||||
/* PY_VECTORCALL_ARGUMENTS_OFFSET is set, so we are allowed to mutate the vector */
|
||||
PyObject **newargs = (PyObject**)args - 1;
|
||||
nargs += 1;
|
||||
PyObject *tmp = newargs[0];
|
||||
newargs[0] = self;
|
||||
assert(newargs[nargs-1]);
|
||||
result = _PyObject_VectorcallTstate(tstate, func, newargs,
|
||||
nargs, kwnames);
|
||||
newargs[0] = tmp;
|
||||
}
|
||||
else {
|
||||
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
|
||||
Py_ssize_t totalargs = nargs + nkwargs;
|
||||
if (totalargs == 0) {
|
||||
return _PyObject_VectorcallTstate(tstate, func, &self, 1, NULL);
|
||||
}
|
||||
|
||||
PyObject *newargs_stack[_PY_FASTCALL_SMALL_STACK];
|
||||
PyObject **newargs;
|
||||
if (totalargs <= (Py_ssize_t)Py_ARRAY_LENGTH(newargs_stack) - 1) {
|
||||
newargs = newargs_stack;
|
||||
}
|
||||
else {
|
||||
newargs = PyMem_Malloc((totalargs+1) * sizeof(PyObject *));
|
||||
if (newargs == NULL) {
|
||||
_PyErr_NoMemory(tstate);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
/* use borrowed references */
|
||||
newargs[0] = self;
|
||||
/* bpo-37138: since totalargs > 0, it's impossible that args is NULL.
|
||||
* We need this, since calling memcpy() with a NULL pointer is
|
||||
* undefined behaviour. */
|
||||
assert(args != NULL);
|
||||
memcpy(newargs + 1, args, totalargs * sizeof(PyObject *));
|
||||
result = _PyObject_VectorcallTstate(tstate, func,
|
||||
newargs, nargs+1, kwnames);
|
||||
if (newargs != newargs_stack) {
|
||||
PyMem_Free(newargs);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return _PyObject_VectorcallPrepend(tstate, func, self, args, nargsf, kwnames);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1479,6 +1479,7 @@ cm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|||
}
|
||||
cm->cm_callable = Py_None;
|
||||
cm->cm_dict = NULL;
|
||||
_PyObject_SetDeferredRefcount((PyObject *)cm);
|
||||
return (PyObject *)cm;
|
||||
}
|
||||
|
||||
|
|
@ -1722,6 +1723,7 @@ sm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|||
}
|
||||
sm->sm_callable = Py_None;
|
||||
sm->sm_dict = NULL;
|
||||
_PyObject_SetDeferredRefcount((PyObject *)sm);
|
||||
return (PyObject *)sm;
|
||||
}
|
||||
|
||||
|
|
@ -1889,3 +1891,17 @@ PyStaticMethod_New(PyObject *callable)
|
|||
}
|
||||
return (PyObject *)sm;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
_PyClassMethod_GetFunc(PyObject *self)
|
||||
{
|
||||
classmethod *cm = _PyClassMethod_CAST(self);
|
||||
return cm->cm_callable;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
_PyStaticMethod_GetFunc(PyObject *self)
|
||||
{
|
||||
staticmethod *sm = _PyStaticMethod_CAST(self);
|
||||
return sm->sm_callable;
|
||||
}
|
||||
|
|
|
|||
137
Objects/object.c
137
Objects/object.c
|
|
@ -10,6 +10,7 @@
|
|||
#include "pycore_descrobject.h" // _PyMethodWrapper_Type
|
||||
#include "pycore_dict.h" // _PyObject_MaterializeManagedDict()
|
||||
#include "pycore_floatobject.h" // _PyFloat_DebugMallocStats()
|
||||
#include "pycore_function.h" // _PyClassMethod_GetFunc()
|
||||
#include "pycore_freelist.h" // _PyObject_ClearFreeLists()
|
||||
#include "pycore_genobject.h" // _PyAsyncGenAThrow_Type
|
||||
#include "pycore_hamt.h" // _PyHamtItems_Type
|
||||
|
|
@ -1664,6 +1665,142 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Look up a method on `self` by `name`.
|
||||
//
|
||||
// On success, `*method` is set and the function returns 0 or 1. If the
|
||||
// return value is 1, the call is an unbound method and `*self` is the
|
||||
// "self" or "cls" argument to pass. If the return value is 0, the call is
|
||||
// a regular function and `*self` is cleared.
|
||||
//
|
||||
// On error, returns -1, clears `*self`, and sets an exception.
|
||||
int
|
||||
_PyObject_GetMethodStackRef(PyThreadState *ts, _PyStackRef *self,
|
||||
PyObject *name, _PyStackRef *method)
|
||||
{
|
||||
int meth_found = 0;
|
||||
PyObject *obj = PyStackRef_AsPyObjectBorrow(*self);
|
||||
|
||||
assert(PyStackRef_IsNull(*method));
|
||||
|
||||
PyTypeObject *tp = Py_TYPE(obj);
|
||||
if (!_PyType_IsReady(tp)) {
|
||||
if (PyType_Ready(tp) < 0) {
|
||||
PyStackRef_CLEAR(*self);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_CheckExact(name)) {
|
||||
PyObject *res = PyObject_GetAttr(obj, name);
|
||||
PyStackRef_CLEAR(*self);
|
||||
if (res != NULL) {
|
||||
*method = PyStackRef_FromPyObjectSteal(res);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
_PyType_LookupStackRefAndVersion(tp, name, method);
|
||||
PyObject *descr = PyStackRef_AsPyObjectBorrow(*method);
|
||||
descrgetfunc f = NULL;
|
||||
if (descr != NULL) {
|
||||
if (_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
|
||||
meth_found = 1;
|
||||
}
|
||||
else {
|
||||
f = Py_TYPE(descr)->tp_descr_get;
|
||||
if (f != NULL && PyDescr_IsData(descr)) {
|
||||
PyObject *value = f(descr, obj, (PyObject *)Py_TYPE(obj));
|
||||
PyStackRef_CLEAR(*method);
|
||||
PyStackRef_CLEAR(*self);
|
||||
if (value != NULL) {
|
||||
*method = PyStackRef_FromPyObjectSteal(value);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
PyObject *dict, *attr;
|
||||
if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) &&
|
||||
_PyObject_TryGetInstanceAttribute(obj, name, &attr)) {
|
||||
if (attr != NULL) {
|
||||
PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectSteal(attr));
|
||||
PyStackRef_CLEAR(*self);
|
||||
return 0;
|
||||
}
|
||||
dict = NULL;
|
||||
}
|
||||
else if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) {
|
||||
dict = (PyObject *)_PyObject_GetManagedDict(obj);
|
||||
}
|
||||
else {
|
||||
PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
|
||||
if (dictptr != NULL) {
|
||||
dict = FT_ATOMIC_LOAD_PTR_ACQUIRE(*dictptr);
|
||||
}
|
||||
else {
|
||||
dict = NULL;
|
||||
}
|
||||
}
|
||||
if (dict != NULL) {
|
||||
assert(PyUnicode_CheckExact(name));
|
||||
int found = _PyDict_GetMethodStackRef((PyDictObject *)dict, name, method);
|
||||
if (found < 0) {
|
||||
assert(PyStackRef_IsNull(*method));
|
||||
PyStackRef_CLEAR(*self);
|
||||
return -1;
|
||||
}
|
||||
else if (found) {
|
||||
PyStackRef_CLEAR(*self);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (meth_found) {
|
||||
assert(!PyStackRef_IsNull(*method));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (f != NULL) {
|
||||
if (Py_IS_TYPE(descr, &PyClassMethod_Type)) {
|
||||
PyObject *callable = _PyClassMethod_GetFunc(descr);
|
||||
PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectNew(callable));
|
||||
PyStackRef_XSETREF(*self, PyStackRef_FromPyObjectNew((PyObject *)tp));
|
||||
return 1;
|
||||
}
|
||||
else if (Py_IS_TYPE(descr, &PyStaticMethod_Type)) {
|
||||
PyObject *callable = _PyStaticMethod_GetFunc(descr);
|
||||
PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectNew(callable));
|
||||
PyStackRef_CLEAR(*self);
|
||||
return 0;
|
||||
}
|
||||
PyObject *value = f(descr, obj, (PyObject *)tp);
|
||||
PyStackRef_CLEAR(*method);
|
||||
PyStackRef_CLEAR(*self);
|
||||
if (value) {
|
||||
*method = PyStackRef_FromPyObjectSteal(value);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (descr != NULL) {
|
||||
assert(!PyStackRef_IsNull(*method));
|
||||
PyStackRef_CLEAR(*self);
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyErr_Format(PyExc_AttributeError,
|
||||
"'%.100s' object has no attribute '%U'",
|
||||
tp->tp_name, name);
|
||||
|
||||
_PyObject_SetAttributeErrorContext(obj, name);
|
||||
assert(PyStackRef_IsNull(*method));
|
||||
PyStackRef_CLEAR(*self);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */
|
||||
|
||||
PyObject *
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -218,6 +218,28 @@ def method(self):
|
|||
obj.method()
|
||||
|
||||
|
||||
class MyClassMethod:
|
||||
@classmethod
|
||||
def my_classmethod(cls):
|
||||
return cls
|
||||
|
||||
@staticmethod
|
||||
def my_staticmethod():
|
||||
pass
|
||||
|
||||
@register_benchmark
|
||||
def classmethod_call():
|
||||
obj = MyClassMethod()
|
||||
for _ in range(1000 * WORK_SCALE):
|
||||
obj.my_classmethod()
|
||||
|
||||
@register_benchmark
|
||||
def staticmethod_call():
|
||||
obj = MyClassMethod()
|
||||
for _ in range(1000 * WORK_SCALE):
|
||||
obj.my_staticmethod()
|
||||
|
||||
|
||||
def bench_one_thread(func):
|
||||
t0 = time.perf_counter_ns()
|
||||
func()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue