]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-139103: fix free-threading `dataclass.__init__` perf issue (gh-141596)
authorEdward Xu <xuxiangad@gmail.com>
Wed, 19 Nov 2025 00:57:59 +0000 (08:57 +0800)
committerGitHub <noreply@github.com>
Wed, 19 Nov 2025 00:57:59 +0000 (00:57 +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.

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 61bcc21ce13d471b1f4b3f269d67a794e33b2575..c99c6b3f6377b60123a13625e8e7e7656957740b 100644 (file)
@@ -6546,6 +6546,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 1a59e25189d5ddca393480f009e90a2452f4edb5..097a065f368f30ffdab103cade15abdd79ca2ee0 100644 (file)
@@ -27,6 +27,7 @@ import queue
 import sys
 import threading
 import time
+from dataclasses import dataclass
 from operator import methodcaller
 
 # The iterations in individual benchmarks are scaled by this factor.
@@ -202,6 +203,17 @@ def method_caller():
     for i in range(1000 * WORK_SCALE):
         mc(obj)
 
+@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()