Skip to content

Commit ce79154

Browse files
authored
gh-139103: fix free-threading dataclass.__init__ perf issue (gh-141596)
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.
1 parent 652c764 commit ce79154

File tree

3 files changed

+25
-0
lines changed

3 files changed

+25
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve multithreaded scaling of dataclasses on the free-threaded build.

Objects/typeobject.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6546,6 +6546,18 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
65466546
assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_INLINE_VALUES));
65476547
assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_MANAGED_DICT));
65486548

6549+
#ifdef Py_GIL_DISABLED
6550+
// gh-139103: Enable deferred refcounting for functions assigned
6551+
// to type objects. This is important for `dataclass.__init__`,
6552+
// which is generated dynamically.
6553+
if (value != NULL &&
6554+
PyFunction_Check(value) &&
6555+
!_PyObject_HasDeferredRefcount(value))
6556+
{
6557+
PyUnstable_Object_EnableDeferredRefcount(value);
6558+
}
6559+
#endif
6560+
65496561
PyObject *old_value = NULL;
65506562
PyObject *descr = _PyType_LookupRef(metatype, name);
65516563
if (descr != NULL) {

Tools/ftscalingbench/ftscalingbench.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import sys
2828
import threading
2929
import time
30+
from dataclasses import dataclass
3031
from operator import methodcaller
3132

3233
# The iterations in individual benchmarks are scaled by this factor.
@@ -202,6 +203,17 @@ def method_caller():
202203
for i in range(1000 * WORK_SCALE):
203204
mc(obj)
204205

206+
@dataclass
207+
class MyDataClass:
208+
x: int
209+
y: int
210+
z: int
211+
212+
@register_benchmark
213+
def instantiate_dataclass():
214+
for _ in range(1000 * WORK_SCALE):
215+
obj = MyDataClass(x=1, y=2, z=3)
216+
205217
def bench_one_thread(func):
206218
t0 = time.perf_counter_ns()
207219
func()

0 commit comments

Comments
 (0)