Skip to content

Commit cdf8eb5

Browse files
committed
Specialize class attribute loads
- Use atomics where appropriate - Only cache deferred values
1 parent 8c78369 commit cdf8eb5

File tree

5 files changed

+27
-22
lines changed

5 files changed

+27
-22
lines changed

Lib/test/test_opcache.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -717,11 +717,16 @@ def write(items):
717717
opname = "FOR_ITER_LIST"
718718
self.assert_races_do_not_crash(opname, get_items, read, write)
719719

720-
@requires_specialization
720+
@requires_specialization_ft
721721
def test_load_attr_class(self):
722722
def get_items():
723+
class B:
724+
pass
725+
723726
class C:
724-
a = object()
727+
# a must be set to an instance that uses deferred reference
728+
# counting
729+
a = B
725730

726731
items = []
727732
for _ in range(self.ITEMS):

Python/bytecodes.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2282,7 +2282,7 @@ dummy_func(
22822282

22832283
EXIT_IF(!PyType_Check(owner_o));
22842284
assert(type_version != 0);
2285-
EXIT_IF(((PyTypeObject *)owner_o)->tp_version_tag != type_version);
2285+
EXIT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(((PyTypeObject *)owner_o)->tp_version_tag) != type_version);
22862286
}
22872287

22882288
split op(_LOAD_ATTR_CLASS, (descr/4, owner -- attr, null if (oparg & 1))) {

Python/executor_cases.c.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/specialize.c

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,9 +1075,7 @@ specialize_attr_loadclassattr(PyObject *owner, _Py_CODEUNIT *instr,
10751075
unsigned int tp_version,
10761076
DescriptorClassification kind, bool is_method,
10771077
uint32_t shared_keys_version);
1078-
#ifndef Py_GIL_DISABLED
10791078
static int specialize_class_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name);
1080-
#endif
10811079

10821080
/* Returns true if instances of obj's class are
10831081
* likely to have `name` in their __dict__.
@@ -1334,12 +1332,7 @@ _Py_Specialize_LoadAttr(_PyStackRef owner_st, _Py_CODEUNIT *instr, PyObject *nam
13341332
fail = specialize_module_load_attr(owner, instr, name);
13351333
}
13361334
else if (PyType_Check(owner)) {
1337-
#ifdef Py_GIL_DISABLED
1338-
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_EXPECTED_ERROR);
1339-
fail = true;
1340-
#else
13411335
fail = specialize_class_load_attr(owner, instr, name);
1342-
#endif
13431336
}
13441337
else {
13451338
fail = specialize_instance_load_attr(owner, instr, name);
@@ -1457,8 +1450,6 @@ _Py_Specialize_StoreAttr(_PyStackRef owner_st, _Py_CODEUNIT *instr, PyObject *na
14571450
Py_XDECREF(descr);
14581451
}
14591452

1460-
#ifndef Py_GIL_DISABLED
1461-
14621453
#ifdef Py_STATS
14631454
static int
14641455
load_attr_fail_kind(DescriptorClassification kind)
@@ -1507,8 +1498,10 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
15071498
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METACLASS_OVERRIDDEN);
15081499
return -1;
15091500
}
1510-
PyObject *metadescriptor = _PyType_Lookup(Py_TYPE(cls), name);
1501+
unsigned int meta_version;
1502+
PyObject *metadescriptor = _PyType_LookupRefAndVersion(Py_TYPE(cls), name, &meta_version);
15111503
DescriptorClassification metakind = classify_descriptor(metadescriptor, false);
1504+
Py_XDECREF(metadescriptor);
15121505
switch (metakind) {
15131506
case METHOD:
15141507
case NON_DESCRIPTOR:
@@ -1525,14 +1518,16 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
15251518
DescriptorClassification kind = 0;
15261519
unsigned int tp_version;
15271520
kind = analyze_descriptor(cls, name, &descr, &tp_version, 0);
1528-
if (type_get_version(cls, LOAD_ATTR) == 0) {
1521+
if (tp_version == 0) {
1522+
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_VERSIONS);
15291523
Py_XDECREF(descr);
15301524
return -1;
15311525
}
15321526
bool metaclass_check = false;
15331527
if ((Py_TYPE(cls)->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) == 0) {
15341528
metaclass_check = true;
1535-
if (type_get_version(Py_TYPE(cls), LOAD_ATTR) == 0) {
1529+
if (meta_version == 0) {
1530+
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_VERSIONS);
15361531
Py_XDECREF(descr);
15371532
return -1;
15381533
}
@@ -1544,10 +1539,17 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
15441539
return -1;
15451540
case METHOD:
15461541
case NON_DESCRIPTOR:
1547-
write_u32(cache->type_version, cls->tp_version_tag);
1542+
#ifdef Py_GIL_DISABLED
1543+
if (!_PyObject_HasDeferredRefcount(descr)) {
1544+
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_DESCR_NOT_DEFERRED);
1545+
Py_XDECREF(descr);
1546+
return -1;
1547+
}
1548+
#endif
1549+
write_u32(cache->type_version, tp_version);
15481550
write_obj(cache->descr, descr);
15491551
if (metaclass_check) {
1550-
write_u32(cache->keys_version, Py_TYPE(cls)->tp_version_tag);
1552+
write_u32(cache->keys_version, meta_version);
15511553
specialize(instr, LOAD_ATTR_CLASS_WITH_METACLASS_CHECK);
15521554
}
15531555
else {
@@ -1568,8 +1570,6 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
15681570
}
15691571
}
15701572

1571-
#endif // Py_GIL_DISABLED
1572-
15731573
// Please collect stats carefully before and after modifying. A subtle change
15741574
// can cause a significant drop in cache hits. A possible test is
15751575
// python.exe -m test_typing test_re test_dis test_zlib.

0 commit comments

Comments
 (0)