Adds `_PyObject_GetMethodStackRef` which uses stackrefs and takes advantage of deferred reference counting in free-threading while calling method objects in vectorcall.
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.
//
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
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;
}
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;
PyObject *result = object_vacall(tstate, obj, callable, vargs);
va_end(vargs);
- Py_DECREF(callable);
+ _PyThreadState_PopCStackRef(tstate, &method);
return result;
}
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;
PyObject *result = object_vacall(tstate, obj, callable, vargs);
va_end(vargs);
- Py_DECREF(callable);
+ _PyThreadState_PopCStackRef(tstate, &method);
return result;
}
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 *
import sys
import threading
import time
+from operator import methodcaller
# The iterations in individual benchmarks are scaled by this factor.
WORK_SCALE = 100
_ = 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()