From e07e29114af50138d2ab360bb72dd6b045b27913 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 15 Aug 2026 19:28:35 -0400 Subject: [PATCH 1/3] Defer reference count mutable class attributes Mutable class attributes would not scale with more threads, adding defered reference counting to resolve it. ./python_main.exe Tools/ftscalingbench/ftscalingbench.py class_attribute Running benchmarks with 18 threads class_attribute 6.3x slower ./python.exe Tools/ftscalingbench/ftscalingbench.py class_attribute Running benchmarks with 18 threads class_attribute 8.0x faster --- Python/specialize.c | 3 +++ Tools/ftscalingbench/ftscalingbench.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Python/specialize.c b/Python/specialize.c index 773b55c329c772..2695fcb85edc00 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 c8a914c22a9e13..609d71ed19df5c 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() From f8f4ae3c3d830bee6b78bd977a345da16c92949c Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 23:32:54 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-08-15-23-32-52.gh-issue-139103.d8pm7q.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-23-32-52.gh-issue-139103.d8pm7q.rst 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 00000000000000..0fd62c333c42e3 --- /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 From 202504c49085e9ad884068157bc5f96556ef773a Mon Sep 17 00:00:00 2001 From: jg Date: Thu, 15 Oct 2026 21:00:15 +0100 Subject: [PATCH 3/3] gh-139103: Test deferred refcount of mutable class attributes Add a free-threading test that reads a class attribute holding an instance of a different class from a non-owning thread and checks that the failed LOAD_ATTR_CLASS specialization attempt enables deferred reference counting on the value, and that the value is still reclaimed by the GC once unreachable. --- Lib/test/test_opcache.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 7946550ec0db63..59f23b86810ce0 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -5,9 +5,11 @@ import threading import types import unittest +import weakref from test.support import (threading_helper, check_impl_detail, - requires_specialization, - cpython_only, requires_jit_disabled, reset_code) + requires_specialization, Py_GIL_DISABLED, + cpython_only, requires_jit_disabled, reset_code, + gc_collect) from test.support.import_helper import import_module # Skip this module on other interpreters, it is cpython specific: @@ -2240,6 +2242,38 @@ def load_enum_member(): self.assert_specialized(load_enum_member, "LOAD_ATTR_CLASS_WITH_METACLASS_CHECK") + @cpython_only + @requires_specialization + @unittest.skipUnless(Py_GIL_DISABLED, + "deferred refcounting needs the free-threaded build") + @threading_helper.requires_working_threading() + def test_load_attr_class_mutable_value_deferred_non_owning_threading(self): + # Ensure load attributes from class mutable values + # are still enabled for deferred referencing + # counting from a non owning thread + class Value: + pass + + class Holder: + attr = Value() + + value = Holder.attr + wr = weakref.ref(value) + + def load_class_attr(): + for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): + x = Holder.attr + assert x is value + + # Ensure we specialise from a non owning thread + threading_helper.run_concurrently(load_class_attr, nthreads=1) + self.assert_no_opcode(load_class_attr, "LOAD_ATTR_CLASS") + self.assertTrue(_testinternalcapi.has_deferred_refcount(value)) + + del load_class_attr, Holder, value + gc_collect() + self.assertIsNone(wr()) + if __name__ == "__main__": unittest.main()