Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions Lib/test/test_opcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enable deferred reference counting for mutable class attributes to improve free-threading scaling
3 changes: 3 additions & 0 deletions Python/specialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 19 additions & 0 deletions Tools/ftscalingbench/ftscalingbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading