Skip to content

Commit 281ea9c

Browse files
authored
bpo-44337: Shrink the LOAD_ATTR/STORE_ATTR caches (GH-31517)
1 parent 78859e5 commit 281ea9c

File tree

4 files changed

+36
-43
lines changed

4 files changed

+36
-43
lines changed

Include/internal/pycore_code.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ typedef struct {
2323

2424
typedef struct {
2525
uint32_t tp_version;
26-
uint32_t dk_version_or_hint;
26+
uint32_t dk_version;
2727
} _PyAttrCache;
2828

2929
typedef struct {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Reduce the memory usage of specialized :opcode:`LOAD_ATTR` and
2+
:opcode:`STORE_ATTR` instructions.

Python/ceval.c

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,11 +1450,10 @@ eval_frame_handle_pending(PyThreadState *tstate)
14501450
#define LOAD_MODULE_ATTR_OR_METHOD(attr_or_method) \
14511451
SpecializedCacheEntry *caches = GET_CACHE(); \
14521452
_PyAdaptiveEntry *cache0 = &caches[0].adaptive; \
1453-
_PyAttrCache *cache1 = &caches[-1].attr; \
14541453
DEOPT_IF(!PyModule_CheckExact(owner), LOAD_##attr_or_method); \
14551454
PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict; \
14561455
assert(dict != NULL); \
1457-
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, \
1456+
DEOPT_IF(dict->ma_keys->dk_version != cache0->version, \
14581457
LOAD_##attr_or_method); \
14591458
assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE); \
14601459
assert(cache0->index < dict->ma_keys->dk_nentries); \
@@ -3452,9 +3451,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
34523451
PyTypeObject *tp = Py_TYPE(owner);
34533452
SpecializedCacheEntry *caches = GET_CACHE();
34543453
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
3455-
_PyAttrCache *cache1 = &caches[-1].attr;
3456-
assert(cache1->tp_version != 0);
3457-
DEOPT_IF(tp->tp_version_tag != cache1->tp_version, LOAD_ATTR);
3454+
assert(cache0->version != 0);
3455+
DEOPT_IF(tp->tp_version_tag != cache0->version, LOAD_ATTR);
34583456
assert(tp->tp_dictoffset < 0);
34593457
assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
34603458
PyDictValues *values = *_PyObject_ValuesPointer(owner);
@@ -3486,15 +3484,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
34863484
PyTypeObject *tp = Py_TYPE(owner);
34873485
SpecializedCacheEntry *caches = GET_CACHE();
34883486
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
3489-
_PyAttrCache *cache1 = &caches[-1].attr;
3490-
assert(cache1->tp_version != 0);
3491-
DEOPT_IF(tp->tp_version_tag != cache1->tp_version, LOAD_ATTR);
3487+
assert(cache0->version != 0);
3488+
DEOPT_IF(tp->tp_version_tag != cache0->version, LOAD_ATTR);
34923489
assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
34933490
PyDictObject *dict = *(PyDictObject **)_PyObject_ManagedDictPointer(owner);
34943491
DEOPT_IF(dict == NULL, LOAD_ATTR);
34953492
assert(PyDict_CheckExact((PyObject *)dict));
34963493
PyObject *name = GETITEM(names, cache0->original_oparg);
3497-
uint32_t hint = cache1->dk_version_or_hint;
3494+
uint16_t hint = cache0->index;
34983495
DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries, LOAD_ATTR);
34993496
PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + hint;
35003497
DEOPT_IF(ep->me_key != name, LOAD_ATTR);
@@ -3514,9 +3511,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
35143511
PyTypeObject *tp = Py_TYPE(owner);
35153512
SpecializedCacheEntry *caches = GET_CACHE();
35163513
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
3517-
_PyAttrCache *cache1 = &caches[-1].attr;
3518-
assert(cache1->tp_version != 0);
3519-
DEOPT_IF(tp->tp_version_tag != cache1->tp_version, LOAD_ATTR);
3514+
assert(cache0->version != 0);
3515+
DEOPT_IF(tp->tp_version_tag != cache0->version, LOAD_ATTR);
35203516
char *addr = (char *)owner + cache0->index;
35213517
res = *(PyObject **)addr;
35223518
DEOPT_IF(res == NULL, LOAD_ATTR);
@@ -3553,9 +3549,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
35533549
PyTypeObject *tp = Py_TYPE(owner);
35543550
SpecializedCacheEntry *caches = GET_CACHE();
35553551
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
3556-
_PyAttrCache *cache1 = &caches[-1].attr;
3557-
assert(cache1->tp_version != 0);
3558-
DEOPT_IF(tp->tp_version_tag != cache1->tp_version, STORE_ATTR);
3552+
assert(cache0->version != 0);
3553+
DEOPT_IF(tp->tp_version_tag != cache0->version, STORE_ATTR);
35593554
assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
35603555
PyDictValues *values = *_PyObject_ValuesPointer(owner);
35613556
DEOPT_IF(values == NULL, STORE_ATTR);
@@ -3581,15 +3576,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
35813576
PyTypeObject *tp = Py_TYPE(owner);
35823577
SpecializedCacheEntry *caches = GET_CACHE();
35833578
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
3584-
_PyAttrCache *cache1 = &caches[-1].attr;
3585-
assert(cache1->tp_version != 0);
3586-
DEOPT_IF(tp->tp_version_tag != cache1->tp_version, STORE_ATTR);
3579+
assert(cache0->version != 0);
3580+
DEOPT_IF(tp->tp_version_tag != cache0->version, STORE_ATTR);
35873581
assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
35883582
PyDictObject *dict = *(PyDictObject **)_PyObject_ManagedDictPointer(owner);
35893583
DEOPT_IF(dict == NULL, STORE_ATTR);
35903584
assert(PyDict_CheckExact((PyObject *)dict));
35913585
PyObject *name = GETITEM(names, cache0->original_oparg);
3592-
uint32_t hint = cache1->dk_version_or_hint;
3586+
uint16_t hint = cache0->index;
35933587
DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries, STORE_ATTR);
35943588
PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + hint;
35953589
DEOPT_IF(ep->me_key != name, STORE_ATTR);
@@ -3616,9 +3610,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
36163610
PyTypeObject *tp = Py_TYPE(owner);
36173611
SpecializedCacheEntry *caches = GET_CACHE();
36183612
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
3619-
_PyAttrCache *cache1 = &caches[-1].attr;
3620-
assert(cache1->tp_version != 0);
3621-
DEOPT_IF(tp->tp_version_tag != cache1->tp_version, STORE_ATTR);
3613+
assert(cache0->version != 0);
3614+
DEOPT_IF(tp->tp_version_tag != cache0->version, STORE_ATTR);
36223615
char *addr = (char *)owner + cache0->index;
36233616
STAT_INC(STORE_ATTR, hit);
36243617
STACK_SHRINK(1);
@@ -4416,7 +4409,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
44164409
assert(self_cls->tp_flags & Py_TPFLAGS_MANAGED_DICT);
44174410
PyDictObject *dict = *(PyDictObject**)_PyObject_ManagedDictPointer(self);
44184411
DEOPT_IF(dict != NULL, LOAD_METHOD);
4419-
DEOPT_IF(((PyHeapTypeObject *)self_cls)->ht_cached_keys->dk_version != cache1->dk_version_or_hint, LOAD_METHOD);
4412+
DEOPT_IF(((PyHeapTypeObject *)self_cls)->ht_cached_keys->dk_version != cache1->dk_version, LOAD_METHOD);
44204413
STAT_INC(LOAD_METHOD, hit);
44214414
PyObject *res = cache2->obj;
44224415
assert(res != NULL);

Python/specialize.c

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ static uint8_t adaptive_opcodes[256] = {
5757

5858
/* The number of cache entries required for a "family" of instructions. */
5959
static uint8_t cache_requirements[256] = {
60-
[LOAD_ATTR] = 2, /* _PyAdaptiveEntry and _PyAttrCache */
60+
[LOAD_ATTR] = 1, // _PyAdaptiveEntry
6161
[LOAD_GLOBAL] = 2, /* _PyAdaptiveEntry and _PyLoadGlobalCache */
6262
[LOAD_METHOD] = 3, /* _PyAdaptiveEntry, _PyAttrCache and _PyObjectCache */
6363
[BINARY_SUBSCR] = 2, /* _PyAdaptiveEntry, _PyObjectCache */
6464
[STORE_SUBSCR] = 0,
6565
[CALL] = 2, /* _PyAdaptiveEntry and _PyObjectCache/_PyCallCache */
6666
[PRECALL] = 2, /* _PyAdaptiveEntry and _PyObjectCache/_PyCallCache */
67-
[STORE_ATTR] = 2, /* _PyAdaptiveEntry and _PyAttrCache */
67+
[STORE_ATTR] = 1, // _PyAdaptiveEntry
6868
[BINARY_OP] = 1, // _PyAdaptiveEntry
6969
[COMPARE_OP] = 1, /* _PyAdaptiveEntry */
7070
[UNPACK_SEQUENCE] = 1, // _PyAdaptiveEntry
@@ -638,7 +638,7 @@ initial_counter_value(void) {
638638
static int
639639
specialize_module_load_attr(
640640
PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
641-
_PyAdaptiveEntry *cache0, _PyAttrCache *cache1, int opcode,
641+
_PyAdaptiveEntry *cache0, int opcode,
642642
int opcode_module)
643643
{
644644
PyModuleObject *m = (PyModuleObject *)owner;
@@ -671,7 +671,7 @@ specialize_module_load_attr(
671671
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_OUT_OF_VERSIONS);
672672
return -1;
673673
}
674-
cache1->dk_version_or_hint = keys_version;
674+
cache0->version = keys_version;
675675
cache0->index = (uint16_t)index;
676676
*instr = _Py_MAKECODEUNIT(opcode_module, _Py_OPARG(*instr));
677677
return 0;
@@ -760,7 +760,7 @@ static int
760760
specialize_dict_access(
761761
PyObject *owner, _Py_CODEUNIT *instr, PyTypeObject *type,
762762
DescriptorClassification kind, PyObject *name,
763-
_PyAdaptiveEntry *cache0, _PyAttrCache *cache1,
763+
_PyAdaptiveEntry *cache0,
764764
int base_op, int values_op, int hint_op)
765765
{
766766
assert(kind == NON_OVERRIDING || kind == NON_DESCRIPTOR || kind == ABSENT ||
@@ -782,7 +782,7 @@ specialize_dict_access(
782782
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_OUT_OF_RANGE);
783783
return 0;
784784
}
785-
cache1->tp_version = type->tp_version_tag;
785+
cache0->version = type->tp_version_tag;
786786
cache0->index = (uint16_t)index;
787787
*instr = _Py_MAKECODEUNIT(values_op, _Py_OPARG(*instr));
788788
}
@@ -795,12 +795,12 @@ specialize_dict_access(
795795
PyObject *value = NULL;
796796
Py_ssize_t hint =
797797
_PyDict_GetItemHint(dict, name, -1, &value);
798-
if (hint != (uint32_t)hint) {
798+
if (hint != (uint16_t)hint) {
799799
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_OUT_OF_RANGE);
800800
return 0;
801801
}
802-
cache1->dk_version_or_hint = (uint32_t)hint;
803-
cache1->tp_version = type->tp_version_tag;
802+
cache0->index = (uint16_t)hint;
803+
cache0->version = type->tp_version_tag;
804804
*instr = _Py_MAKECODEUNIT(hint_op, _Py_OPARG(*instr));
805805
}
806806
return 1;
@@ -810,9 +810,8 @@ int
810810
_Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache)
811811
{
812812
_PyAdaptiveEntry *cache0 = &cache->adaptive;
813-
_PyAttrCache *cache1 = &cache[-1].attr;
814813
if (PyModule_CheckExact(owner)) {
815-
int err = specialize_module_load_attr(owner, instr, name, cache0, cache1,
814+
int err = specialize_module_load_attr(owner, instr, name, cache0,
816815
LOAD_ATTR, LOAD_ATTR_MODULE);
817816
if (err) {
818817
goto fail;
@@ -853,7 +852,7 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
853852
assert(dmem->type == T_OBJECT_EX);
854853
assert(offset > 0);
855854
cache0->index = (uint16_t)offset;
856-
cache1->tp_version = type->tp_version_tag;
855+
cache0->version = type->tp_version_tag;
857856
*instr = _Py_MAKECODEUNIT(LOAD_ATTR_SLOT, _Py_OPARG(*instr));
858857
goto success;
859858
}
@@ -862,7 +861,7 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
862861
Py_ssize_t offset = offsetof(PyObject, ob_type);
863862
assert(offset == (uint16_t)offset);
864863
cache0->index = (uint16_t)offset;
865-
cache1->tp_version = type->tp_version_tag;
864+
cache0->version = type->tp_version_tag;
866865
*instr = _Py_MAKECODEUNIT(LOAD_ATTR_SLOT, _Py_OPARG(*instr));
867866
goto success;
868867
}
@@ -883,7 +882,7 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
883882
break;
884883
}
885884
int err = specialize_dict_access(
886-
owner, instr, type, kind, name, cache0, cache1,
885+
owner, instr, type, kind, name, cache0,
887886
LOAD_ATTR, LOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_WITH_HINT
888887
);
889888
if (err < 0) {
@@ -908,7 +907,6 @@ int
908907
_Py_Specialize_StoreAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache)
909908
{
910909
_PyAdaptiveEntry *cache0 = &cache->adaptive;
911-
_PyAttrCache *cache1 = &cache[-1].attr;
912910
PyTypeObject *type = Py_TYPE(owner);
913911
if (PyModule_CheckExact(owner)) {
914912
SPECIALIZATION_FAIL(STORE_ATTR, SPEC_FAIL_OVERRIDDEN);
@@ -942,7 +940,7 @@ _Py_Specialize_StoreAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, S
942940
assert(dmem->type == T_OBJECT_EX);
943941
assert(offset > 0);
944942
cache0->index = (uint16_t)offset;
945-
cache1->tp_version = type->tp_version_tag;
943+
cache0->version = type->tp_version_tag;
946944
*instr = _Py_MAKECODEUNIT(STORE_ATTR_SLOT, _Py_OPARG(*instr));
947945
goto success;
948946
}
@@ -965,7 +963,7 @@ _Py_Specialize_StoreAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, S
965963
}
966964

967965
int err = specialize_dict_access(
968-
owner, instr, type, kind, name, cache0, cache1,
966+
owner, instr, type, kind, name, cache0,
969967
STORE_ATTR, STORE_ATTR_INSTANCE_VALUE, STORE_ATTR_WITH_HINT
970968
);
971969
if (err < 0) {
@@ -1066,7 +1064,7 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
10661064

10671065
PyTypeObject *owner_cls = Py_TYPE(owner);
10681066
if (PyModule_CheckExact(owner)) {
1069-
int err = specialize_module_load_attr(owner, instr, name, cache0, cache1,
1067+
int err = specialize_module_load_attr(owner, instr, name, cache0,
10701068
LOAD_METHOD, LOAD_METHOD_MODULE);
10711069
if (err) {
10721070
goto fail;
@@ -1111,7 +1109,7 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
11111109
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_OUT_OF_VERSIONS);
11121110
goto fail;
11131111
}
1114-
cache1->dk_version_or_hint = keys_version;
1112+
cache1->dk_version = keys_version;
11151113
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_CACHED, _Py_OPARG(*instr));
11161114
}
11171115
else {

0 commit comments

Comments
 (0)