diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-23-32-52.gh-issue-139103.d8pm7q.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-23-32-52.gh-issue-139103.d8pm7q.rst new file mode 100644 index 000000000000000..0fd62c333c42e3d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-23-32-52.gh-issue-139103.d8pm7q.rst @@ -0,0 +1 @@ +Enable deferred reference counting for mutable class attributes to improve free-threading scaling diff --git a/Python/specialize.c b/Python/specialize.c index 773b55c329c7720..2695fcb85edc00a 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -1216,6 +1216,9 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr, // special case for enums which has Py_TYPE(descr) == cls // so guarding on type version is sufficient if (Py_TYPE(descr) != cls) { +#ifdef Py_GIL_DISABLED + maybe_enable_deferred_ref_count(descr); +#endif SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_MUTABLE_CLASS); Py_XDECREF(descr); return -1; diff --git a/Tools/ftscalingbench/ftscalingbench.py b/Tools/ftscalingbench/ftscalingbench.py index c8a914c22a9e137..609d71ed19df5cd 100644 --- a/Tools/ftscalingbench/ftscalingbench.py +++ b/Tools/ftscalingbench/ftscalingbench.py @@ -326,6 +326,25 @@ def enum_attr(): MyEnum.Z +class _SharedAttrValue: + pass + +class MyClassWithSharedAttr: + # A class attribute whose value is an instance of a *different* class. + # Reading it from multiple threads did not scale because LOAD_ATTR_CLASS + # cannot specialize this case (the value's type is not the owner class), + # leaving the shared value's reference count contended on every read. + attr = _SharedAttrValue() + +@register_benchmark +def class_attribute(): + obj = MyClassWithSharedAttr + for _ in range(1000 * WORK_SCALE): + obj.attr + obj.attr + obj.attr + + def bench_one_thread(func): t0 = time.perf_counter_ns() func()