Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 326f0ba

Browse files
authored
GH-106485: Dematerialize instance dictionaries when possible (GH-106539)
1 parent a9caf9c commit 326f0ba

File tree

10 files changed

+88
-24
lines changed

10 files changed

+88
-24
lines changed

Include/internal/pycore_dict.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ extern "C" {
1010
#endif
1111

1212
#include "pycore_dict_state.h"
13+
#include "pycore_object.h"
1314
#include "pycore_runtime.h" // _PyRuntime
1415

1516
// Unsafe flavor of PyDict_GetItemWithError(): no error checking
@@ -62,6 +63,8 @@ extern uint32_t _PyDictKeys_GetVersionForCurrentState(
6263

6364
extern size_t _PyDict_KeysSize(PyDictKeysObject *keys);
6465

66+
extern void _PyDictKeys_DecRef(PyDictKeysObject *keys);
67+
6568
/* _Py_dict_lookup() returns index of entry which can be used like DK_ENTRIES(dk)[index].
6669
* -1 when no entry found, -3 when compare raises error.
6770
*/
@@ -196,6 +199,7 @@ _PyDict_NotifyEvent(PyInterpreterState *interp,
196199
}
197200

198201
extern PyObject *_PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values);
202+
extern int _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv);
199203
extern PyObject *_PyDict_FromItems(
200204
PyObject *const *keys, Py_ssize_t keys_offset,
201205
PyObject *const *values, Py_ssize_t values_offset,

Include/pystats.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ typedef struct _object_stats {
6565
uint64_t dict_materialized_new_key;
6666
uint64_t dict_materialized_too_big;
6767
uint64_t dict_materialized_str_subclass;
68+
uint64_t dict_dematerialized;
6869
uint64_t type_cache_hits;
6970
uint64_t type_cache_misses;
7071
uint64_t type_cache_dunder_hits;

Lib/test/test_opcache.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -865,8 +865,10 @@ class C:
865865
items = []
866866
for _ in range(self.ITEMS):
867867
item = C()
868-
item.__dict__
869868
item.a = None
869+
# Resize into a combined unicode dict:
870+
for i in range(29):
871+
setattr(item, f"_{i}", None)
870872
items.append(item)
871873
return items
872874

@@ -932,7 +934,9 @@ class C:
932934
items = []
933935
for _ in range(self.ITEMS):
934936
item = C()
935-
item.__dict__
937+
# Resize into a combined unicode dict:
938+
for i in range(29):
939+
setattr(item, f"_{i}", None)
936940
items.append(item)
937941
return items
938942

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Reduce the number of materialized instances dictionaries by dematerializing
2+
them when possible.

Objects/dictobject.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5464,6 +5464,35 @@ _PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values)
54645464
return make_dict_from_instance_attributes(interp, keys, values);
54655465
}
54665466

5467+
// Return 1 if the dict was dematerialized, 0 otherwise.
5468+
int
5469+
_PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv)
5470+
{
5471+
assert(_PyObject_DictOrValuesPointer(obj) == dorv);
5472+
assert(!_PyDictOrValues_IsValues(*dorv));
5473+
PyDictObject *dict = (PyDictObject *)_PyDictOrValues_GetDict(*dorv);
5474+
if (dict == NULL) {
5475+
return 0;
5476+
}
5477+
// It's likely that this dict still shares its keys (if it was materialized
5478+
// on request and not heavily modified):
5479+
assert(PyDict_CheckExact(dict));
5480+
assert(_PyType_HasFeature(Py_TYPE(obj), Py_TPFLAGS_HEAPTYPE));
5481+
if (dict->ma_keys != CACHED_KEYS(Py_TYPE(obj)) || Py_REFCNT(dict) != 1) {
5482+
return 0;
5483+
}
5484+
assert(dict->ma_values);
5485+
// We have an opportunity to do something *really* cool: dematerialize it!
5486+
_PyDictKeys_DecRef(dict->ma_keys);
5487+
_PyDictOrValues_SetValues(dorv, dict->ma_values);
5488+
OBJECT_STAT_INC(dict_dematerialized);
5489+
// Don't try this at home, kids:
5490+
dict->ma_keys = NULL;
5491+
dict->ma_values = NULL;
5492+
Py_DECREF(dict);
5493+
return 1;
5494+
}
5495+
54675496
int
54685497
_PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
54695498
PyObject *name, PyObject *value)
@@ -5688,6 +5717,7 @@ PyObject_GenericGetDict(PyObject *obj, void *context)
56885717
dict = _PyDictOrValues_GetDict(*dorv_ptr);
56895718
if (dict == NULL) {
56905719
dictkeys_incref(CACHED_KEYS(tp));
5720+
OBJECT_STAT_INC(dict_materialized_on_request);
56915721
dict = new_dict_with_shared_keys(interp, CACHED_KEYS(tp));
56925722
dorv_ptr->dict = dict;
56935723
}
@@ -5731,6 +5761,9 @@ _PyObjectDict_SetItem(PyTypeObject *tp, PyObject **dictptr,
57315761
dict = *dictptr;
57325762
if (dict == NULL) {
57335763
dictkeys_incref(cached);
5764+
if (_PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT)) {
5765+
OBJECT_STAT_INC(dict_materialized_on_request);
5766+
}
57345767
dict = new_dict_with_shared_keys(interp, cached);
57355768
if (dict == NULL)
57365769
return -1;

Objects/typeobject.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4965,9 +4965,6 @@ type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
49654965
return res;
49664966
}
49674967

4968-
extern void
4969-
_PyDictKeys_DecRef(PyDictKeysObject *keys);
4970-
49714968

49724969
static void
49734970
type_dealloc_common(PyTypeObject *type)

Python/bytecodes.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,8 +1827,10 @@ dummy_func(
18271827
op(_CHECK_MANAGED_OBJECT_HAS_VALUES, (owner -- owner)) {
18281828
assert(Py_TYPE(owner)->tp_dictoffset < 0);
18291829
assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
1830-
PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner);
1831-
DEOPT_IF(!_PyDictOrValues_IsValues(dorv), LOAD_ATTR);
1830+
PyDictOrValues *dorv = _PyObject_DictOrValuesPointer(owner);
1831+
DEOPT_IF(!_PyDictOrValues_IsValues(*dorv) &&
1832+
!_PyObject_MakeInstanceAttributesFromDict(owner, dorv),
1833+
LOAD_ATTR);
18321834
}
18331835

18341836
op(_LOAD_ATTR_INSTANCE_VALUE, (index/1, owner -- attr, null if (oparg & 1))) {
@@ -2727,8 +2729,10 @@ dummy_func(
27272729
assert(type_version != 0);
27282730
DEOPT_IF(owner_cls->tp_version_tag != type_version, LOAD_ATTR);
27292731
assert(owner_cls->tp_flags & Py_TPFLAGS_MANAGED_DICT);
2730-
PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner);
2731-
DEOPT_IF(!_PyDictOrValues_IsValues(dorv), LOAD_ATTR);
2732+
PyDictOrValues *dorv = _PyObject_DictOrValuesPointer(owner);
2733+
DEOPT_IF(!_PyDictOrValues_IsValues(*dorv) &&
2734+
!_PyObject_MakeInstanceAttributesFromDict(owner, dorv),
2735+
LOAD_ATTR);
27322736
PyHeapTypeObject *owner_heap_type = (PyHeapTypeObject *)owner_cls;
27332737
DEOPT_IF(owner_heap_type->ht_cached_keys->dk_version !=
27342738
keys_version, LOAD_ATTR);
@@ -2757,8 +2761,10 @@ dummy_func(
27572761
assert(type_version != 0);
27582762
DEOPT_IF(owner_cls->tp_version_tag != type_version, LOAD_ATTR);
27592763
assert(owner_cls->tp_flags & Py_TPFLAGS_MANAGED_DICT);
2760-
PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner);
2761-
DEOPT_IF(!_PyDictOrValues_IsValues(dorv), LOAD_ATTR);
2764+
PyDictOrValues *dorv = _PyObject_DictOrValuesPointer(owner);
2765+
DEOPT_IF(!_PyDictOrValues_IsValues(*dorv) &&
2766+
!_PyObject_MakeInstanceAttributesFromDict(owner, dorv),
2767+
LOAD_ATTR);
27622768
PyHeapTypeObject *owner_heap_type = (PyHeapTypeObject *)owner_cls;
27632769
DEOPT_IF(owner_heap_type->ht_cached_keys->dk_version !=
27642770
keys_version, LOAD_ATTR);

Python/executor_cases.c.h

Lines changed: 4 additions & 2 deletions
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: 12 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/specialize.c

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ print_object_stats(FILE *out, ObjectStats *stats)
192192
fprintf(out, "Object materialize dict (new key): %" PRIu64 "\n", stats->dict_materialized_new_key);
193193
fprintf(out, "Object materialize dict (too big): %" PRIu64 "\n", stats->dict_materialized_too_big);
194194
fprintf(out, "Object materialize dict (str subclass): %" PRIu64 "\n", stats->dict_materialized_str_subclass);
195+
fprintf(out, "Object dematerialize dict: %" PRIu64 "\n", stats->dict_dematerialized);
195196
fprintf(out, "Object method cache hits: %" PRIu64 "\n", stats->type_cache_hits);
196197
fprintf(out, "Object method cache misses: %" PRIu64 "\n", stats->type_cache_misses);
197198
fprintf(out, "Object method cache collisions: %" PRIu64 "\n", stats->type_cache_collisions);
@@ -685,8 +686,10 @@ specialize_dict_access(
685686
return 0;
686687
}
687688
_PyAttrCache *cache = (_PyAttrCache *)(instr + 1);
688-
PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner);
689-
if (_PyDictOrValues_IsValues(dorv)) {
689+
PyDictOrValues *dorv = _PyObject_DictOrValuesPointer(owner);
690+
if (_PyDictOrValues_IsValues(*dorv) ||
691+
_PyObject_MakeInstanceAttributesFromDict(owner, dorv))
692+
{
690693
// Virtual dictionary
691694
PyDictKeysObject *keys = ((PyHeapTypeObject *)type)->ht_cached_keys;
692695
assert(PyUnicode_CheckExact(name));
@@ -704,12 +707,16 @@ specialize_dict_access(
704707
instr->op.code = values_op;
705708
}
706709
else {
707-
PyDictObject *dict = (PyDictObject *)_PyDictOrValues_GetDict(dorv);
710+
PyDictObject *dict = (PyDictObject *)_PyDictOrValues_GetDict(*dorv);
708711
if (dict == NULL || !PyDict_CheckExact(dict)) {
709712
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_NO_DICT);
710713
return 0;
711714
}
712715
// We found an instance with a __dict__.
716+
if (dict->ma_values) {
717+
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_ATTR_NON_STRING_OR_SPLIT);
718+
return 0;
719+
}
713720
Py_ssize_t index =
714721
_PyDict_LookupIndex(dict, name);
715722
if (index != (uint16_t)index) {
@@ -1100,9 +1107,11 @@ PyObject *descr, DescriptorClassification kind, bool is_method)
11001107
assert(descr != NULL);
11011108
assert((is_method && kind == METHOD) || (!is_method && kind == NON_DESCRIPTOR));
11021109
if (owner_cls->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
1103-
PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner);
1110+
PyDictOrValues *dorv = _PyObject_DictOrValuesPointer(owner);
11041111
PyDictKeysObject *keys = ((PyHeapTypeObject *)owner_cls)->ht_cached_keys;
1105-
if (!_PyDictOrValues_IsValues(dorv)) {
1112+
if (!_PyDictOrValues_IsValues(*dorv) &&
1113+
!_PyObject_MakeInstanceAttributesFromDict(owner, dorv))
1114+
{
11061115
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_HAS_MANAGED_DICT);
11071116
return 0;
11081117
}

0 commit comments

Comments
 (0)