From 83193b5e32f4b066eb396814e94528c68a2d484a Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 23 Apr 2026 19:07:32 +0530 Subject: [PATCH 1/5] fix scaling of descriptors on free-threading --- Objects/typeobject.c | 17 ++++++++++++----- Tools/ftscalingbench/ftscalingbench.py | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fb3c7101410683..98e93a4567a7a9 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6289,6 +6289,10 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef PyObject *res_obj = PyStackRef_AsPyObjectBorrow(*out); #if Py_GIL_DISABLED + // Optimistically enable deferred refcounting for the result if it's not already enabled. + if (res_obj != NULL && PyType_IS_GC(Py_TYPE(res_obj)) && !_PyObject_HasDeferredRefcount(res_obj)) { + PyUnstable_Object_EnableDeferredRefcount(res_obj); + } update_cache_gil_disabled(entry, name, version_tag, res_obj); #else PyObject *old_value = update_cache(entry, name, version_tag, res_obj); @@ -10995,10 +10999,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) @@ -11010,9 +11016,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)} From dabfd091d7f67b014862902f96c47c9e181e4b15 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 23 Apr 2026 19:17:23 +0530 Subject: [PATCH 2/5] drc only for descriptors --- Objects/typeobject.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 98e93a4567a7a9..636c07f9213575 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6289,8 +6289,11 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef PyObject *res_obj = PyStackRef_AsPyObjectBorrow(*out); #if Py_GIL_DISABLED - // Optimistically enable deferred refcounting for the result if it's not already enabled. - if (res_obj != NULL && PyType_IS_GC(Py_TYPE(res_obj)) && !_PyObject_HasDeferredRefcount(res_obj)) { + // Optimistically enable deferred refcounting on descriptors + if (res_obj != NULL && + PyType_IS_GC(Py_TYPE(res_obj)) && + !_PyObject_HasDeferredRefcount(res_obj) && + Py_TYPE(res_obj)->tp_descr_get != NULL) { PyUnstable_Object_EnableDeferredRefcount(res_obj); } update_cache_gil_disabled(entry, name, version_tag, res_obj); From 125aaa13c5e9343fe9358f97afaeb68743d9f178 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Wed, 13 May 2026 16:04:54 +0530 Subject: [PATCH 3/5] make drc in type_setattro --- Objects/typeobject.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index abd0a45bf570e6..1386ecfd5a3752 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4841,6 +4841,20 @@ 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 (PyType_IS_GC(Py_TYPE(value)) && !_PyObject_HasDeferredRefcount(value) && + (PyFunction_Check(value) || Py_TYPE(value)->tp_descr_get != NULL)) + { + PyUnstable_Object_EnableDeferredRefcount(value); + } + } +#endif + return 0; } @@ -6350,13 +6364,6 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef PyObject *res_obj = PyStackRef_AsPyObjectBorrow(*out); #if Py_GIL_DISABLED - // Optimistically enable deferred refcounting on descriptors - if (res_obj != NULL && - PyType_IS_GC(Py_TYPE(res_obj)) && - !_PyObject_HasDeferredRefcount(res_obj) && - Py_TYPE(res_obj)->tp_descr_get != NULL) { - PyUnstable_Object_EnableDeferredRefcount(res_obj); - } update_cache_gil_disabled(entry, name, version_tag, res_obj); #else PyObject *old_value = update_cache(entry, name, version_tag, res_obj); @@ -6753,12 +6760,14 @@ 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. + // 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) && - !_PyObject_HasDeferredRefcount(value)) + !_PyObject_HasDeferredRefcount(value) && + PyType_IS_GC(Py_TYPE(value)) && + (PyFunction_Check(value) || Py_TYPE(value)->tp_descr_get != NULL)) { PyUnstable_Object_EnableDeferredRefcount(value); } From 03182a470b8130c90de4e39fa6459ca7bc441905 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Wed, 13 May 2026 16:27:21 +0530 Subject: [PATCH 4/5] fix is gc type bug in type_setattro --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 1386ecfd5a3752..8ba708c367dc8e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6765,8 +6765,8 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value) // which is generated dynamically, and for descriptor scaling on // free-threaded builds. if (value != NULL && - !_PyObject_HasDeferredRefcount(value) && PyType_IS_GC(Py_TYPE(value)) && + !_PyObject_HasDeferredRefcount(value) && (PyFunction_Check(value) || Py_TYPE(value)->tp_descr_get != NULL)) { PyUnstable_Object_EnableDeferredRefcount(value); From b89a5d907af6f2949f52219408cc91cf003a7309 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Wed, 13 May 2026 22:20:05 +0530 Subject: [PATCH 5/5] remove redundant checks --- Objects/typeobject.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 8ba708c367dc8e..7cca137f74be58 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4847,9 +4847,7 @@ type_new_set_attrs(const type_new_ctx *ctx, PyTypeObject *type) Py_ssize_t pos = 0; PyObject *key, *value; while (PyDict_Next(dict, &pos, &key, &value)) { - if (PyType_IS_GC(Py_TYPE(value)) && !_PyObject_HasDeferredRefcount(value) && - (PyFunction_Check(value) || Py_TYPE(value)->tp_descr_get != NULL)) - { + if (PyFunction_Check(value) || Py_TYPE(value)->tp_descr_get != NULL) { PyUnstable_Object_EnableDeferredRefcount(value); } } @@ -6764,10 +6762,7 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value) // 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 && - PyType_IS_GC(Py_TYPE(value)) && - !_PyObject_HasDeferredRefcount(value) && - (PyFunction_Check(value) || Py_TYPE(value)->tp_descr_get != NULL)) + if (value != NULL && (PyFunction_Check(value) || Py_TYPE(value)->tp_descr_get != NULL)) { PyUnstable_Object_EnableDeferredRefcount(value); }