Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d623c99

Browse filesBrowse files
markshannonasvetlov
authored andcommitted
bpo-45107: Specialize LOAD_METHOD for instances with dict. (GH-31531)
1 parent e6892d7 commit d623c99
Copy full SHA for d623c99

File tree

7 files changed

+143
-66
lines changed
Filter options

7 files changed

+143
-66
lines changed

‎Include/internal/pycore_object.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_object.h
+2Lines 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

Copy file name to clipboardExpand all lines: Include/opcode.h
+36-35Lines 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

Copy file name to clipboardExpand all lines: Lib/opcode.py
+2-1Lines 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",
+1Lines 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

Copy file name to clipboardExpand all lines: Python/ceval.c
+34-2Lines 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

Copy file name to clipboardExpand all lines: Python/opcode_targets.h
+15-15Lines 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

Copy file name to clipboardExpand all lines: Python/specialize.c
+53-13Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.