gh-134043: use stackrefs in vectorcalling methods (#134044)
Adds `_PyObject_GetMethodStackRef` which uses stackrefs and takes advantage of deferred reference counting in free-threading while calling method objects in vectorcall.
This commit is contained in:
parent
3f9eb55e09
commit
a380d57873
@ -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, PyObject *obj,
|
||||
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.
|
||||
//
|
||||
|
@ -834,12 +834,15 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
|
||||
assert(PyVectorcall_NARGS(nargsf) >= 1);
|
||||
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
PyObject *callable = NULL;
|
||||
_PyCStackRef method;
|
||||
_PyThreadState_PushCStackRef(tstate, &method);
|
||||
/* Use args[0] as "self" argument */
|
||||
int unbound = _PyObject_GetMethod(args[0], name, &callable);
|
||||
if (callable == NULL) {
|
||||
int unbound = _PyObject_GetMethodStackRef(tstate, args[0], name, &method.ref);
|
||||
if (PyStackRef_IsNull(method.ref)) {
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
return NULL;
|
||||
}
|
||||
PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
|
||||
|
||||
if (unbound) {
|
||||
/* We must remove PY_VECTORCALL_ARGUMENTS_OFFSET since
|
||||
@ -855,7 +858,7 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
|
||||
PyObject *result = _PyObject_VectorcallTstate(tstate, callable,
|
||||
args, nargsf, kwnames);
|
||||
Py_DECREF(callable);
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -868,11 +871,14 @@ 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 method;
|
||||
_PyThreadState_PushCStackRef(tstate, &method);
|
||||
int is_method = _PyObject_GetMethodStackRef(tstate, obj, name, &method.ref);
|
||||
if (PyStackRef_IsNull(method.ref)) {
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
return NULL;
|
||||
}
|
||||
PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
|
||||
obj = is_method ? obj : NULL;
|
||||
|
||||
va_list vargs;
|
||||
@ -880,7 +886,7 @@ PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
|
||||
PyObject *result = object_vacall(tstate, obj, callable, vargs);
|
||||
va_end(vargs);
|
||||
|
||||
Py_DECREF(callable);
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -897,12 +903,15 @@ _PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...)
|
||||
if (!oname) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *callable = NULL;
|
||||
int is_method = _PyObject_GetMethod(obj, oname, &callable);
|
||||
if (callable == NULL) {
|
||||
_PyCStackRef method;
|
||||
_PyThreadState_PushCStackRef(tstate, &method);
|
||||
int is_method = _PyObject_GetMethodStackRef(tstate, obj, oname, &method.ref);
|
||||
if (PyStackRef_IsNull(method.ref)) {
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
return NULL;
|
||||
}
|
||||
PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
|
||||
|
||||
obj = is_method ? obj : NULL;
|
||||
|
||||
va_list vargs;
|
||||
@ -910,7 +919,7 @@ _PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...)
|
||||
PyObject *result = object_vacall(tstate, obj, callable, vargs);
|
||||
va_end(vargs);
|
||||
|
||||
Py_DECREF(callable);
|
||||
_PyThreadState_PopCStackRef(tstate, &method);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
110
Objects/object.c
110
Objects/object.c
@ -1664,6 +1664,116 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
_PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj,
|
||||
PyObject *name, _PyStackRef *method)
|
||||
{
|
||||
int meth_found = 0;
|
||||
|
||||
assert(PyStackRef_IsNull(*method));
|
||||
|
||||
PyTypeObject *tp = Py_TYPE(obj);
|
||||
if (!_PyType_IsReady(tp)) {
|
||||
if (PyType_Ready(tp) < 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_CheckExact(name)) {
|
||||
PyObject *res = PyObject_GetAttr(obj, name);
|
||||
if (res != NULL) {
|
||||
*method = PyStackRef_FromPyObjectSteal(res);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
_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);
|
||||
if (value != NULL) {
|
||||
*method = PyStackRef_FromPyObjectSteal(value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
PyObject *dict, *attr;
|
||||
if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) &&
|
||||
_PyObject_TryGetInstanceAttribute(obj, name, &attr)) {
|
||||
if (attr != NULL) {
|
||||
PyStackRef_CLEAR(*method);
|
||||
*method = PyStackRef_FromPyObjectSteal(attr);
|
||||
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) {
|
||||
// TODO: use _Py_dict_lookup_threadsafe_stackref
|
||||
Py_INCREF(dict);
|
||||
PyObject *value;
|
||||
if (PyDict_GetItemRef(dict, name, &value) != 0) {
|
||||
// found or error
|
||||
Py_DECREF(dict);
|
||||
PyStackRef_CLEAR(*method);
|
||||
if (value != NULL) {
|
||||
*method = PyStackRef_FromPyObjectSteal(value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// not found
|
||||
Py_DECREF(dict);
|
||||
}
|
||||
|
||||
if (meth_found) {
|
||||
assert(!PyStackRef_IsNull(*method));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (f != NULL) {
|
||||
PyObject *value = f(descr, obj, (PyObject *)Py_TYPE(obj));
|
||||
PyStackRef_CLEAR(*method);
|
||||
if (value) {
|
||||
*method = PyStackRef_FromPyObjectSteal(value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (descr != NULL) {
|
||||
assert(!PyStackRef_IsNull(*method));
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyErr_Format(PyExc_AttributeError,
|
||||
"'%.100s' object has no attribute '%U'",
|
||||
tp->tp_name, name);
|
||||
|
||||
_PyObject_SetAttributeErrorContext(obj, name);
|
||||
assert(PyStackRef_IsNull(*method));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */
|
||||
|
||||
PyObject *
|
||||
|
@ -27,6 +27,7 @@ import queue
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from operator import methodcaller
|
||||
|
||||
# The iterations in individual benchmarks are scaled by this factor.
|
||||
WORK_SCALE = 100
|
||||
@ -188,6 +189,18 @@ def thread_local_read():
|
||||
_ = tmp.x
|
||||
_ = tmp.x
|
||||
|
||||
class MyClass:
|
||||
__slots__ = ()
|
||||
|
||||
def func(self):
|
||||
pass
|
||||
|
||||
@register_benchmark
|
||||
def method_caller():
|
||||
mc = methodcaller("func")
|
||||
obj = MyClass()
|
||||
for i in range(1000 * WORK_SCALE):
|
||||
mc(obj)
|
||||
|
||||
def bench_one_thread(func):
|
||||
t0 = time.perf_counter_ns()
|
||||
|
Loading…
x
Reference in New Issue
Block a user