Skip to content

Commit b7c25ea

Browse files
[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 ce79154) Co-authored-by: Edward Xu <[email protected]>
1 parent 8c79688 commit b7c25ea

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
@@ -6181,6 +6181,18 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
61816181
assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_INLINE_VALUES));
61826182
assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_MANAGED_DICT));
61836183

6184+
#ifdef Py_GIL_DISABLED
6185+
// gh-139103: Enable deferred refcounting for functions assigned
6186+
// to type objects. This is important for `dataclass.__init__`,
6187+
// which is generated dynamically.
6188+
if (value != NULL &&
6189+
PyFunction_Check(value) &&
6190+
!_PyObject_HasDeferredRefcount(value))
6191+
{
6192+
PyUnstable_Object_EnableDeferredRefcount(value);
6193+
}
6194+
#endif
6195+
61846196
PyObject *old_value = NULL;
61856197
PyObject *descr = _PyType_LookupRef(metatype, name);
61866198
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

3132
# The iterations in individual benchmarks are scaled by this factor.
3233
WORK_SCALE = 100
@@ -189,6 +190,17 @@ def thread_local_read():
189190
_ = tmp.x
190191

191192

193+
@dataclass
194+
class MyDataClass:
195+
x: int
196+
y: int
197+
z: int
198+
199+
@register_benchmark
200+
def instantiate_dataclass():
201+
for _ in range(1000 * WORK_SCALE):
202+
obj = MyDataClass(x=1, y=2, z=3)
203+
192204
def bench_one_thread(func):
193205
t0 = time.perf_counter_ns()
194206
func()

0 commit comments

Comments
 (0)