mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
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
4 changed files with 148 additions and 13 deletions
|
@ -897,6 +897,9 @@ extern PyObject *_PyType_LookupRefAndVersion(PyTypeObject *, PyObject *,
|
||||||
extern unsigned int
|
extern unsigned int
|
||||||
_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out);
|
_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
|
// Cache the provided init method in the specialization cache of type if the
|
||||||
// provided type version matches the current version of the type.
|
// 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);
|
assert(PyVectorcall_NARGS(nargsf) >= 1);
|
||||||
|
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
PyObject *callable = NULL;
|
_PyCStackRef method;
|
||||||
|
_PyThreadState_PushCStackRef(tstate, &method);
|
||||||
/* Use args[0] as "self" argument */
|
/* Use args[0] as "self" argument */
|
||||||
int unbound = _PyObject_GetMethod(args[0], name, &callable);
|
int unbound = _PyObject_GetMethodStackRef(tstate, args[0], name, &method.ref);
|
||||||
if (callable == NULL) {
|
if (PyStackRef_IsNull(method.ref)) {
|
||||||
|
_PyThreadState_PopCStackRef(tstate, &method);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
|
||||||
|
|
||||||
if (unbound) {
|
if (unbound) {
|
||||||
/* We must remove PY_VECTORCALL_ARGUMENTS_OFFSET since
|
/* 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);
|
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
|
||||||
PyObject *result = _PyObject_VectorcallTstate(tstate, callable,
|
PyObject *result = _PyObject_VectorcallTstate(tstate, callable,
|
||||||
args, nargsf, kwnames);
|
args, nargsf, kwnames);
|
||||||
Py_DECREF(callable);
|
_PyThreadState_PopCStackRef(tstate, &method);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -868,11 +871,14 @@ PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
|
||||||
return null_error(tstate);
|
return null_error(tstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *callable = NULL;
|
_PyCStackRef method;
|
||||||
int is_method = _PyObject_GetMethod(obj, name, &callable);
|
_PyThreadState_PushCStackRef(tstate, &method);
|
||||||
if (callable == NULL) {
|
int is_method = _PyObject_GetMethodStackRef(tstate, obj, name, &method.ref);
|
||||||
|
if (PyStackRef_IsNull(method.ref)) {
|
||||||
|
_PyThreadState_PopCStackRef(tstate, &method);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
|
||||||
obj = is_method ? obj : NULL;
|
obj = is_method ? obj : NULL;
|
||||||
|
|
||||||
va_list vargs;
|
va_list vargs;
|
||||||
|
@ -880,7 +886,7 @@ PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
|
||||||
PyObject *result = object_vacall(tstate, obj, callable, vargs);
|
PyObject *result = object_vacall(tstate, obj, callable, vargs);
|
||||||
va_end(vargs);
|
va_end(vargs);
|
||||||
|
|
||||||
Py_DECREF(callable);
|
_PyThreadState_PopCStackRef(tstate, &method);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -897,12 +903,15 @@ _PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...)
|
||||||
if (!oname) {
|
if (!oname) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
_PyCStackRef method;
|
||||||
PyObject *callable = NULL;
|
_PyThreadState_PushCStackRef(tstate, &method);
|
||||||
int is_method = _PyObject_GetMethod(obj, oname, &callable);
|
int is_method = _PyObject_GetMethodStackRef(tstate, obj, oname, &method.ref);
|
||||||
if (callable == NULL) {
|
if (PyStackRef_IsNull(method.ref)) {
|
||||||
|
_PyThreadState_PopCStackRef(tstate, &method);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
|
||||||
|
|
||||||
obj = is_method ? obj : NULL;
|
obj = is_method ? obj : NULL;
|
||||||
|
|
||||||
va_list vargs;
|
va_list vargs;
|
||||||
|
@ -910,7 +919,7 @@ _PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...)
|
||||||
PyObject *result = object_vacall(tstate, obj, callable, vargs);
|
PyObject *result = object_vacall(tstate, obj, callable, vargs);
|
||||||
va_end(vargs);
|
va_end(vargs);
|
||||||
|
|
||||||
Py_DECREF(callable);
|
_PyThreadState_PopCStackRef(tstate, &method);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
110
Objects/object.c
110
Objects/object.c
|
@ -1664,6 +1664,116 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
|
||||||
return 0;
|
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. */
|
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
|
|
|
@ -27,6 +27,7 @@ import queue
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from operator import methodcaller
|
||||||
|
|
||||||
# The iterations in individual benchmarks are scaled by this factor.
|
# The iterations in individual benchmarks are scaled by this factor.
|
||||||
WORK_SCALE = 100
|
WORK_SCALE = 100
|
||||||
|
@ -188,6 +189,18 @@ def thread_local_read():
|
||||||
_ = tmp.x
|
_ = tmp.x
|
||||||
_ = 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):
|
def bench_one_thread(func):
|
||||||
t0 = time.perf_counter_ns()
|
t0 = time.perf_counter_ns()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue