https://github.com/python/cpython/commit/94bca40ff09c20f6168d6a27e3aa42bf8a8077b8
commit: 94bca40ff09c20f6168d6a27e3aa42bf8a8077b8
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-05-13T23:03:59+05:30
summary:
gh-148906: fix performance scaling of descriptors on free-threading (#148915)
files:
M Objects/typeobject.c
M Tools/ftscalingbench/ftscalingbench.py
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 9a18ca72516da7..7cca137f74be58 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -4841,6 +4841,18 @@ type_new_set_attrs(const type_new_ctx *ctx, PyTypeObject
*type)
if (type_new_set_classdictcell(dict) < 0) {
return -1;
}
+
+#ifdef Py_GIL_DISABLED
+ // enable deferred reference counting on functions and descriptors
+ Py_ssize_t pos = 0;
+ PyObject *key, *value;
+ while (PyDict_Next(dict, &pos, &key, &value)) {
+ if (PyFunction_Check(value) || Py_TYPE(value)->tp_descr_get != NULL) {
+ PyUnstable_Object_EnableDeferredRefcount(value);
+ }
+ }
+#endif
+
return 0;
}
@@ -6746,12 +6758,11 @@ type_setattro(PyObject *self, PyObject *name, PyObject
*value)
assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_MANAGED_DICT));
#ifdef Py_GIL_DISABLED
- // gh-139103: Enable deferred refcounting for functions assigned
- // to type objects. This is important for `dataclass.__init__`,
- // which is generated dynamically.
- if (value != NULL &&
- PyFunction_Check(value) &&
- !_PyObject_HasDeferredRefcount(value))
+ // gh-139103: Enable deferred refcounting for functions and descriptors
+ // assigned to type objects. This is important for `dataclass.__init__`,
+ // which is generated dynamically, and for descriptor scaling on
+ // free-threaded builds.
+ if (value != NULL && (PyFunction_Check(value) ||
Py_TYPE(value)->tp_descr_get != NULL))
{
PyUnstable_Object_EnableDeferredRefcount(value);
}
@@ -11089,10 +11100,12 @@ static PyObject *
slot_tp_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
PyTypeObject *tp = Py_TYPE(self);
- PyObject *get;
-
- get = _PyType_LookupRef(tp, &_Py_ID(__get__));
- if (get == NULL) {
+ PyThreadState *tstate = _PyThreadState_GET();
+ _PyCStackRef cref;
+ _PyThreadState_PushCStackRef(tstate, &cref);
+ _PyType_LookupStackRefAndVersion(tp, &_Py_ID(__get__), &cref.ref);
+ if (PyStackRef_IsNull(cref.ref)) {
+ _PyThreadState_PopCStackRef(tstate, &cref);
#ifndef Py_GIL_DISABLED
/* Avoid further slowdowns */
if (tp->tp_descr_get == slot_tp_descr_get)
@@ -11104,9 +11117,10 @@ slot_tp_descr_get(PyObject *self, PyObject *obj,
PyObject *type)
obj = Py_None;
if (type == NULL)
type = Py_None;
+ PyObject *get = PyStackRef_AsPyObjectBorrow(cref.ref);
PyObject *stack[3] = {self, obj, type};
PyObject *res = PyObject_Vectorcall(get, stack, 3, NULL);
- Py_DECREF(get);
+ _PyThreadState_PopCStackRef(tstate, &cref);
return res;
}
diff --git a/Tools/ftscalingbench/ftscalingbench.py
b/Tools/ftscalingbench/ftscalingbench.py
index 60f43b99c0f69d..c8a914c22a9e13 100644
--- a/Tools/ftscalingbench/ftscalingbench.py
+++ b/Tools/ftscalingbench/ftscalingbench.py
@@ -279,6 +279,23 @@ def staticmethod_call():
for _ in range(1000 * WORK_SCALE):
obj.my_staticmethod()
+
+class MyDescriptor:
+ def __get__(self, obj, objtype=None):
+ return 42
+
+ def __set__(self, obj, value):
+ pass
+
+class MyClassWithDescriptor:
+ attr = MyDescriptor()
+
+@register_benchmark
+def descriptor():
+ obj = MyClassWithDescriptor()
+ for _ in range(1000 * WORK_SCALE):
+ obj.attr
+
@register_benchmark
def deepcopy():
x = {'list': [1, 2], 'tuple': (1, None)}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]