From: Sam Gross Date: Wed, 19 Nov 2025 15:00:51 +0000 (-0500) Subject: [3.14] gh-139103: fix free-threading `dataclass.__init__` perf issue (gh-141596)... X-Git-Tag: v3.14.1~87 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b7c25eabd6d93e745c7216b42d026e6a36795724;p=thirdparty%2FPython%2Fcpython.git [3.14] gh-139103: fix free-threading `dataclass.__init__` perf issue (gh-141596) (gh-141750) The dataclasses `__init__` function is generated dynamically by a call to `exec()` and so doesn't have deferred reference counting enabled. Enable deferred reference counting on functions when assigned as an attribute to type objects to avoid reference count contention when creating dataclass instances. (cherry picked from commit ce791541769a41beabec0f515cd62e504d46ff1c) Co-authored-by: Edward Xu --- diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-15-23-58-23.gh-issue-139103.9cVYJ0.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-15-23-58-23.gh-issue-139103.9cVYJ0.rst new file mode 100644 index 000000000000..c038dc742cce --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-15-23-58-23.gh-issue-139103.9cVYJ0.rst @@ -0,0 +1 @@ +Improve multithreaded scaling of dataclasses on the free-threaded build. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0c6c597dd8b8..05d12bf6bbaf 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6181,6 +6181,18 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value) assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_INLINE_VALUES)); 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)) + { + PyUnstable_Object_EnableDeferredRefcount(value); + } +#endif + PyObject *old_value = NULL; PyObject *descr = _PyType_LookupRef(metatype, name); if (descr != NULL) { diff --git a/Tools/ftscalingbench/ftscalingbench.py b/Tools/ftscalingbench/ftscalingbench.py index 926bc66b944c..b815376b7ed5 100644 --- a/Tools/ftscalingbench/ftscalingbench.py +++ b/Tools/ftscalingbench/ftscalingbench.py @@ -27,6 +27,7 @@ import queue import sys import threading import time +from dataclasses import dataclass # The iterations in individual benchmarks are scaled by this factor. WORK_SCALE = 100 @@ -189,6 +190,17 @@ def thread_local_read(): _ = tmp.x +@dataclass +class MyDataClass: + x: int + y: int + z: int + +@register_benchmark +def instantiate_dataclass(): + for _ in range(1000 * WORK_SCALE): + obj = MyDataClass(x=1, y=2, z=3) + def bench_one_thread(func): t0 = time.perf_counter_ns() func()