]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-139103: fix free-threading `dataclass.__init__` perf issue (gh-141596)...
authorSam Gross <colesbury@gmail.com>
Wed, 19 Nov 2025 15:00:51 +0000 (10:00 -0500)
committerGitHub <noreply@github.com>
Wed, 19 Nov 2025 15:00:51 +0000 (15:00 +0000)
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 <xuxiangad@gmail.com>
Misc/NEWS.d/next/Core_and_Builtins/2025-11-15-23-58-23.gh-issue-139103.9cVYJ0.rst [new file with mode: 0644]
Objects/typeobject.c
Tools/ftscalingbench/ftscalingbench.py

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 (file)
index 0000000..c038dc7
--- /dev/null
@@ -0,0 +1 @@
+Improve multithreaded scaling of dataclasses on the free-threaded build.
index 0c6c597dd8b8bfa309e29415f8ad2612bef65781..05d12bf6bbaf5c84298112af10967cdacbb163e1 100644 (file)
@@ -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) {
index 926bc66b944c6ffbe39d8591705845fd24eee398..b815376b7ed56f8df1c9fed237163c8205b84a94 100644 (file)
@@ -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()