-
-
Notifications
You must be signed in to change notification settings - Fork 33.8k
Description
Feature or enhancement
Currently the calling APIs such as PyObject_VectorcallMethod use _PyObject_GetMethod to avoid creating a bound method object, however _PyObject_GetMethod increfs and decrefs the object even when the underlying object supports deferred reference counting. This leads to reference counting contention on the object and it doesn't scale well in free threading. This API is heavily used by modules such as asyncio so it is important that _PyObject_GetMethod should scale well with threads.
Lines 829 to 859 in 54a6875
| PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, | |
| size_t nargsf, PyObject *kwnames) | |
| { | |
| assert(name != NULL); | |
| assert(args != NULL); | |
| assert(PyVectorcall_NARGS(nargsf) >= 1); | |
| PyThreadState *tstate = _PyThreadState_GET(); | |
| PyObject *callable = NULL; | |
| /* Use args[0] as "self" argument */ | |
| int unbound = _PyObject_GetMethod(args[0], name, &callable); | |
| if (callable == NULL) { | |
| 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 { | |
| /* Skip "self". We can keep PY_VECTORCALL_ARGUMENTS_OFFSET since | |
| * args[-1] in the onward call is args[0] here. */ | |
| args++; | |
| nargsf--; | |
| } | |
| EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable); | |
| PyObject *result = _PyObject_VectorcallTstate(tstate, callable, | |
| args, nargsf, kwnames); | |
| Py_DECREF(callable); | |
| return result; |
Proposal:
To take advantage of deferred reference counting, we should add stack ref variant of _PyObject_GetMethod and use it in all calling APIs to avoid reference counting contention on the function object.
Implementation:
- The new stack ref variant
_PyObject_GetMethodStackRefwill use_PyType_LookupStackRefAndVersionwhen looking up method from type cache. - A new stack ref variant of
_PyObject_TryGetInstanceAttributeStackRefwill be added to to be used with_PyObject_GetMethodStackRef. - Possibly we can avoid incref and decref when looking up the attribute on instance dict by delaying freeing of object's dictionary and using
_Py_dict_lookup_threadsafe_stackref.Lines 1632 to 1640 in 54a6875
Py_INCREF(dict); if (PyDict_GetItemRef(dict, name, method) != 0) { // found or error Py_DECREF(dict); Py_XDECREF(descr); return 0; } // not found Py_DECREF(dict);