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.
This commit is contained in:
Edward Xu 2025-11-19 08:57:59 +08:00 committed by GitHub
parent 652c764a59
commit ce79154176
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 25 additions and 0 deletions

View file

@ -0,0 +1 @@
Improve multithreaded scaling of dataclasses on the free-threaded build.

View 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) {

View file

@ -27,6 +27,7 @@
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()