Skip to content

Commit 2a6ece5

Browse files
authored
bpo-45107: Specialize LOAD_METHOD for instances with dict. (GH-31531)
1 parent 4dc7463 commit 2a6ece5

File tree

7 files changed

+143
-66
lines changed

7 files changed

+143
-66
lines changed

Include/internal/pycore_object.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ static inline PyObject **_PyObject_ManagedDictPointer(PyObject *obj)
227227
return ((PyObject **)obj)-3;
228228
}
229229

230+
#define MANAGED_DICT_OFFSET (((int)sizeof(PyObject *))*-3)
231+
230232
extern PyObject ** _PyObject_DictPointer(PyObject *);
231233
extern int _PyObject_VisitInstanceAttributes(PyObject *self, visitproc visit, void *arg);
232234
extern void _PyObject_ClearInstanceAttributes(PyObject *self);

Include/opcode.h

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

Lib/opcode.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,11 @@ def jabs_op(name, op):
260260
"LOAD_GLOBAL_MODULE",
261261
"LOAD_GLOBAL_BUILTIN",
262262
"LOAD_METHOD_ADAPTIVE",
263-
"LOAD_METHOD_CACHED",
264263
"LOAD_METHOD_CLASS",
265264
"LOAD_METHOD_MODULE",
266265
"LOAD_METHOD_NO_DICT",
266+
"LOAD_METHOD_WITH_DICT",
267+
"LOAD_METHOD_WITH_VALUES",
267268
"PRECALL_ADAPTIVE",
268269
"PRECALL_BUILTIN_CLASS",
269270
"PRECALL_NO_KW_BUILTIN_O",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Specialize ``LOAD_METHOD`` for instances with a dict.

Python/ceval.c

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4424,15 +4424,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
44244424
}
44254425
}
44264426

4427-
TARGET(LOAD_METHOD_CACHED) {
4427+
TARGET(LOAD_METHOD_WITH_VALUES) {
44284428
/* LOAD_METHOD, with cached method object */
44294429
assert(cframe.use_tracing == 0);
44304430
PyObject *self = TOP();
44314431
PyTypeObject *self_cls = Py_TYPE(self);
44324432
SpecializedCacheEntry *caches = GET_CACHE();
44334433
_PyAttrCache *cache1 = &caches[-1].attr;
44344434
_PyObjectCache *cache2 = &caches[-2].obj;
4435-
4435+
assert(cache1->tp_version != 0);
44364436
DEOPT_IF(self_cls->tp_version_tag != cache1->tp_version, LOAD_METHOD);
44374437
assert(self_cls->tp_flags & Py_TPFLAGS_MANAGED_DICT);
44384438
PyDictObject *dict = *(PyDictObject**)_PyObject_ManagedDictPointer(self);
@@ -4448,6 +4448,38 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
44484448
NOTRACE_DISPATCH();
44494449
}
44504450

4451+
TARGET(LOAD_METHOD_WITH_DICT) {
4452+
/* LOAD_METHOD, with a dict
4453+
Can be either a managed dict, or a tp_dictoffset offset.*/
4454+
assert(cframe.use_tracing == 0);
4455+
PyObject *self = TOP();
4456+
PyTypeObject *self_cls = Py_TYPE(self);
4457+
SpecializedCacheEntry *caches = GET_CACHE();
4458+
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
4459+
_PyAttrCache *cache1 = &caches[-1].attr;
4460+
_PyObjectCache *cache2 = &caches[-2].obj;
4461+
4462+
DEOPT_IF(self_cls->tp_version_tag != cache1->tp_version, LOAD_METHOD);
4463+
/* Treat index as a signed 16 bit value */
4464+
int dictoffset = *(int16_t *)&cache0->index;
4465+
PyDictObject **dictptr = (PyDictObject**)(((char *)self)+dictoffset);
4466+
assert(
4467+
dictoffset == MANAGED_DICT_OFFSET ||
4468+
(dictoffset == self_cls->tp_dictoffset && dictoffset > 0)
4469+
);
4470+
PyDictObject *dict = *dictptr;
4471+
DEOPT_IF(dict == NULL, LOAD_METHOD);
4472+
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version, LOAD_METHOD);
4473+
STAT_INC(LOAD_METHOD, hit);
4474+
PyObject *res = cache2->obj;
4475+
assert(res != NULL);
4476+
assert(_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR));
4477+
Py_INCREF(res);
4478+
SET_TOP(res);
4479+
PUSH(self);
4480+
NOTRACE_DISPATCH();
4481+
}
4482+
44514483
TARGET(LOAD_METHOD_NO_DICT) {
44524484
assert(cframe.use_tracing == 0);
44534485
PyObject *self = TOP();

Python/opcode_targets.h

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

Python/specialize.c

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,13 @@ specialize_class_load_method(PyObject *owner, _Py_CODEUNIT *instr, PyObject *nam
10621062
}
10631063
}
10641064

1065+
typedef enum {
1066+
MANAGED_VALUES = 1,
1067+
MANAGED_DICT = 2,
1068+
OFFSET_DICT = 3,
1069+
NO_DICT = 4
1070+
} ObjectDictKind;
1071+
10651072
// Please collect stats carefully before and after modifying. A subtle change
10661073
// can cause a significant drop in cache hits. A possible test is
10671074
// python.exe -m test_typing test_re test_dis test_zlib.
@@ -1071,8 +1078,8 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
10711078
_PyAdaptiveEntry *cache0 = &cache->adaptive;
10721079
_PyAttrCache *cache1 = &cache[-1].attr;
10731080
_PyObjectCache *cache2 = &cache[-2].obj;
1074-
10751081
PyTypeObject *owner_cls = Py_TYPE(owner);
1082+
10761083
if (PyModule_CheckExact(owner)) {
10771084
int err = specialize_module_load_attr(owner, instr, name, cache0,
10781085
LOAD_METHOD, LOAD_METHOD_MODULE);
@@ -1102,13 +1109,39 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
11021109
SPECIALIZATION_FAIL(LOAD_METHOD, load_method_fail_kind(kind));
11031110
goto fail;
11041111
}
1112+
ObjectDictKind dictkind;
1113+
PyDictKeysObject *keys;
11051114
if (owner_cls->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
1106-
PyObject **owner_dictptr = _PyObject_ManagedDictPointer(owner);
1107-
if (*owner_dictptr) {
1108-
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_LOAD_METHOD_HAS_MANAGED_DICT);
1115+
PyObject *dict = *_PyObject_ManagedDictPointer(owner);
1116+
keys = ((PyHeapTypeObject *)owner_cls)->ht_cached_keys;
1117+
if (dict == NULL) {
1118+
dictkind = MANAGED_VALUES;
1119+
}
1120+
else {
1121+
dictkind = MANAGED_DICT;
1122+
}
1123+
}
1124+
else {
1125+
Py_ssize_t dictoffset = owner_cls->tp_dictoffset;
1126+
if (dictoffset < 0 || dictoffset > INT16_MAX) {
1127+
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_OUT_OF_RANGE);
11091128
goto fail;
11101129
}
1111-
PyDictKeysObject *keys = ((PyHeapTypeObject *)owner_cls)->ht_cached_keys;
1130+
if (dictoffset == 0) {
1131+
dictkind = NO_DICT;
1132+
keys = NULL;
1133+
}
1134+
else {
1135+
PyObject *dict = *(PyObject **) ((char *)owner + dictoffset);
1136+
if (dict == NULL) {
1137+
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_NO_DICT);
1138+
goto fail;
1139+
}
1140+
keys = ((PyDictObject *)dict)->ma_keys;
1141+
dictkind = OFFSET_DICT;
1142+
}
1143+
}
1144+
if (dictkind != NO_DICT) {
11121145
Py_ssize_t index = _PyDictKeys_StringLookup(keys, name);
11131146
if (index != DKIX_EMPTY) {
11141147
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_LOAD_METHOD_IS_ATTR);
@@ -1120,16 +1153,23 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
11201153
goto fail;
11211154
}
11221155
cache1->dk_version = keys_version;
1123-
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_CACHED, _Py_OPARG(*instr));
11241156
}
1125-
else {
1126-
if (owner_cls->tp_dictoffset == 0) {
1157+
switch(dictkind) {
1158+
case NO_DICT:
11271159
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_NO_DICT, _Py_OPARG(*instr));
1128-
}
1129-
else {
1130-
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_LOAD_METHOD_HAS_DICT);
1131-
goto fail;
1132-
}
1160+
break;
1161+
case MANAGED_VALUES:
1162+
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_WITH_VALUES, _Py_OPARG(*instr));
1163+
break;
1164+
case MANAGED_DICT:
1165+
*(int16_t *)&cache0->index = (int16_t)MANAGED_DICT_OFFSET;
1166+
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_WITH_DICT, _Py_OPARG(*instr));
1167+
break;
1168+
case OFFSET_DICT:
1169+
assert(owner_cls->tp_dictoffset > 0 && owner_cls->tp_dictoffset <= INT16_MAX);
1170+
cache0->index = (uint16_t)owner_cls->tp_dictoffset;
1171+
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_WITH_DICT, _Py_OPARG(*instr));
1172+
break;
11331173
}
11341174
/* `descr` is borrowed. This is safe for methods (even inherited ones from
11351175
* super classes!) as long as tp_version_tag is validated for two main reasons:

0 commit comments

Comments
 (0)