Skip to content

bpo-44889: Specialize LOAD_METHOD with PEP 659 adaptive interpreter #27722

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
60ab351
WIP: implement LOAD_METHOD_HINT
Fidget-Spinner Aug 6, 2021
e0f7ebe
fix most tests: validate object __dict__ didn't modify keys
Fidget-Spinner Aug 6, 2021
ffd7ff3
fix all tests and allow no owner.__dict__ to be specialized!
Fidget-Spinner Aug 7, 2021
2b014a2
specialize builtins too
Fidget-Spinner Aug 7, 2021
b200887
turn off specialization stats
Fidget-Spinner Aug 7, 2021
d977986
improve specialization
Fidget-Spinner Aug 7, 2021
187fddb
fix a comment
Fidget-Spinner Aug 7, 2021
c71f142
better attribute detection - fix all tests
Fidget-Spinner Aug 7, 2021
f3082f4
always check attr inside instance __dict__
Fidget-Spinner Aug 7, 2021
0f32d60
backoff immediately for LOAD_METHOD, fail on dict subclasses, optimiz…
Fidget-Spinner Aug 8, 2021
8a31db3
Improve comments and specialization stats
Fidget-Spinner Aug 9, 2021
06d606e
Merge remote-tracking branch 'upstream/main' into specialize_load_method
Fidget-Spinner Aug 10, 2021
608f6d0
regen opcode
Fidget-Spinner Aug 10, 2021
1f5e64a
simplify code, use new spec_fail stats style
Fidget-Spinner Aug 10, 2021
29daf44
LOAD_METHOD_WITH_HINT now supports inherited methods
Fidget-Spinner Aug 10, 2021
a3d6dd3
remove unneeded deopt
Fidget-Spinner Aug 10, 2021
d7ee4ac
remove experiments, rename to LOAD_METHOD_CACHED
Fidget-Spinner Aug 11, 2021
c944af0
move deopt earlier
Fidget-Spinner Aug 11, 2021
2f5cc63
Apply Mark's suggestions, fix up comments
Fidget-Spinner Aug 11, 2021
70ad1ea
Create 2021-08-11-20-45-02.bpo-44889.2T3nTn.rst
Fidget-Spinner Aug 11, 2021
f4487fc
remove unused variable
Fidget-Spinner Aug 11, 2021
afcf51e
round two: apply mark's suggestions
Fidget-Spinner Aug 11, 2021
e551d7a
Merge remote-tracking branch 'upstream/main' into specialize_load_method
Fidget-Spinner Aug 11, 2021
6f1f1f0
remove TARGET
Fidget-Spinner Aug 11, 2021
dc152ed
add to specialization stats dict
Fidget-Spinner Aug 11, 2021
dd77f75
improve borrowed ref safety explanation
Fidget-Spinner Aug 11, 2021
601e9b8
implement LOAD_METHOD_MODULE, and LOAD_METHOD_CLASS (for python classes)
Fidget-Spinner Aug 12, 2021
94b6106
fix macro
Fidget-Spinner Aug 12, 2021
1d87e53
Partially address Mark's review
Fidget-Spinner Aug 12, 2021
3697d4f
refactor, fully address review
Fidget-Spinner Aug 12, 2021
1d83667
fix check for classes
Fidget-Spinner Aug 12, 2021
5db1d3b
update comments, LOAD_METHOD_BUILTIN isn't worth it
Fidget-Spinner Aug 12, 2021
d12205b
apply mark's refactoring suggestions
Fidget-Spinner Aug 13, 2021
8af701d
more refactoring
Fidget-Spinner Aug 13, 2021
ad4ff6e
refactor and address review comments
Fidget-Spinner Aug 13, 2021
a3d1b50
add cases, use pytype_check only
Fidget-Spinner Aug 13, 2021
d8384bc
address review number 100
Fidget-Spinner Aug 13, 2021
ae2a520
remove usless comment
Fidget-Spinner Aug 13, 2021
a12406b
Fix buildbot errors: cache the type/class and compare that too
Fidget-Spinner Aug 13, 2021
33445d5
Revert "Fix buildbot errors: cache the type/class and compare that too"
Fidget-Spinner Aug 15, 2021
6d806a2
add a few more checks to safeguard specializations
Fidget-Spinner Aug 16, 2021
020a326
Merge remote-tracking branch 'upstream/main' into specialize_load_method
Fidget-Spinner Aug 16, 2021
d27e388
Fix for types with no dict
Fidget-Spinner Aug 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ typedef struct {
uint32_t builtin_keys_version;
} _PyLoadGlobalCache;

typedef struct {
/* Borrowed ref in LOAD_METHOD */
PyObject *obj;
} _PyObjectCache;

/* Add specialized versions of entries to this union.
*
* Do not break the invariant: sizeof(SpecializedCacheEntry) == 8
Expand All @@ -45,6 +50,7 @@ typedef union {
_PyAdaptiveEntry adaptive;
_PyAttrCache attr;
_PyLoadGlobalCache load_global;
_PyObjectCache obj;
} SpecializedCacheEntry;

#define INSTRUCTIONS_PER_ENTRY (sizeof(SpecializedCacheEntry)/sizeof(_Py_CODEUNIT))
Expand Down Expand Up @@ -299,6 +305,7 @@ cache_backoff(_PyAdaptiveEntry *entry) {
int _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);
int _Py_Specialize_StoreAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);
int _Py_Specialize_LoadGlobal(PyObject *globals, PyObject *builtins, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);
int _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);
int _Py_Specialize_BinarySubscr(PyObject *sub, PyObject *container, _Py_CODEUNIT *instr);

#define PRINT_SPECIALIZATION_STATS 0
Expand Down
22 changes: 13 additions & 9 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ def jabs_op(name, op):
"LOAD_GLOBAL_ADAPTIVE",
"LOAD_GLOBAL_MODULE",
"LOAD_GLOBAL_BUILTIN",
"LOAD_METHOD_ADAPTIVE",
"LOAD_METHOD_CACHED",
"LOAD_METHOD_CLASS",
"LOAD_METHOD_MODULE",
"STORE_ATTR_ADAPTIVE",
"STORE_ATTR_SPLIT_KEYS",
"STORE_ATTR_SLOT",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Initial implementation of adaptive specialization of ``LOAD_METHOD``. The
following specialized forms were added:

* ``LOAD_METHOD_CACHED``

* ``LOAD_METHOD_MODULE``

* ``LOAD_METHOD_CLASS``
142 changes: 127 additions & 15 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,27 @@ eval_frame_handle_pending(PyThreadState *tstate)
#define BUILTINS() frame->f_builtins
#define LOCALS() frame->f_locals

/* Shared opcode macros */

// shared by LOAD_ATTR_MODULE and LOAD_METHOD_MODULE
#define LOAD_MODULE_ATTR_OR_METHOD(attr_or_method) \
SpecializedCacheEntry *caches = GET_CACHE(); \
_PyAdaptiveEntry *cache0 = &caches[0].adaptive; \
_PyAttrCache *cache1 = &caches[-1].attr; \
DEOPT_IF(!PyModule_CheckExact(owner), LOAD_##attr_or_method); \
PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict; \
assert(dict != NULL); \
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, \
LOAD_##attr_or_method); \
assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE); \
assert(cache0->index < dict->ma_keys->dk_nentries); \
PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + cache0->index; \
res = ep->me_value; \
DEOPT_IF(res == NULL, LOAD_##attr_or_method); \
STAT_INC(LOAD_##attr_or_method, hit); \
record_cache_hit(cache0); \
Py_INCREF(res);

static int
trace_function_entry(PyThreadState *tstate, InterpreterFrame *frame)
{
Expand Down Expand Up @@ -3511,23 +3532,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr

TARGET(LOAD_ATTR_MODULE): {
assert(cframe.use_tracing == 0);
// shared with LOAD_METHOD_MODULE
PyObject *owner = TOP();
PyObject *res;
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyAttrCache *cache1 = &caches[-1].attr;
DEOPT_IF(!PyModule_CheckExact(owner), LOAD_ATTR);
PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict;
assert(dict != NULL);
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, LOAD_ATTR);
assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE);
assert(cache0->index < dict->ma_keys->dk_nentries);
PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + cache0->index;
res = ep->me_value;
DEOPT_IF(res == NULL, LOAD_ATTR);
STAT_INC(LOAD_ATTR, hit);
record_cache_hit(cache0);
Py_INCREF(res);
LOAD_MODULE_ATTR_OR_METHOD(ATTR);
SET_TOP(res);
Py_DECREF(owner);
DISPATCH();
Expand Down Expand Up @@ -4282,6 +4290,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
}

TARGET(LOAD_METHOD): {
PREDICTED(LOAD_METHOD);
STAT_INC(LOAD_METHOD, unquickened);
/* Designed to work in tandem with CALL_METHOD. */
PyObject *name = GETITEM(names, oparg);
PyObject *obj = TOP();
Expand Down Expand Up @@ -4318,6 +4328,107 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
DISPATCH();
}

TARGET(LOAD_METHOD_ADAPTIVE): {
assert(cframe.use_tracing == 0);
SpecializedCacheEntry *cache = GET_CACHE();
if (cache->adaptive.counter == 0) {
PyObject *owner = TOP();
PyObject *name = GETITEM(names, cache->adaptive.original_oparg);
next_instr--;
if (_Py_Specialize_LoadMethod(owner, next_instr, name, cache) < 0) {
goto error;
}
DISPATCH();
}
else {
STAT_INC(LOAD_METHOD, deferred);
cache->adaptive.counter--;
oparg = cache->adaptive.original_oparg;
STAT_DEC(LOAD_METHOD, unquickened);
JUMP_TO_INSTRUCTION(LOAD_METHOD);
}
}

TARGET(LOAD_METHOD_CACHED): {
/* LOAD_METHOD, with cached method object */
assert(cframe.use_tracing == 0);
PyObject *self = TOP();
PyTypeObject *self_cls = Py_TYPE(self);
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyAttrCache *cache1 = &caches[-1].attr;
_PyObjectCache *cache2 = &caches[-2].obj;

DEOPT_IF(self_cls->tp_version_tag != cache1->tp_version, LOAD_METHOD);
assert(cache1->dk_version_or_hint != 0);
assert(cache1->tp_version != 0);
assert(self_cls->tp_dictoffset >= 0);
assert(Py_TYPE(self_cls)->tp_dictoffset > 0);

// inline version of _PyObject_GetDictPtr for offset >= 0
PyObject *dict = self_cls->tp_dictoffset != 0 ?
*(PyObject **) ((char *)self + self_cls->tp_dictoffset) : NULL;

// Ensure self.__dict__ didn't modify keys.
// Don't care if self has no dict, it could be builtin or __slots__.
DEOPT_IF(dict != NULL &&
((PyDictObject *)dict)->ma_keys->dk_version !=
cache1->dk_version_or_hint, LOAD_METHOD);

STAT_INC(LOAD_METHOD, hit);
record_cache_hit(cache0);
PyObject *res = cache2->obj;
assert(res != NULL);
assert(_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR));
Py_INCREF(res);
SET_TOP(res);
PUSH(self);
DISPATCH();
}

TARGET(LOAD_METHOD_MODULE): {
assert(cframe.use_tracing == 0);
PyObject *owner = TOP();
PyObject *res;
LOAD_MODULE_ATTR_OR_METHOD(METHOD);
SET_TOP(NULL);
Py_DECREF(owner);
PUSH(res);
DISPATCH();
}

TARGET(LOAD_METHOD_CLASS): {
/* LOAD_METHOD, for class methods */
assert(cframe.use_tracing == 0);
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyAttrCache *cache1 = &caches[-1].attr;
_PyObjectCache *cache2 = &caches[-2].obj;

PyObject *cls = TOP();
PyTypeObject *cls_type = Py_TYPE(cls);
assert(cls_type->tp_dictoffset > 0);
PyObject *dict = *(PyObject **) ((char *)cls + cls_type->tp_dictoffset);
// Don't care if no dict -- tp_version_tag should catch anything wrong.
DEOPT_IF(dict != NULL && ((PyDictObject *)dict)->ma_keys->dk_version !=
cache1->dk_version_or_hint, LOAD_METHOD);
DEOPT_IF(((PyTypeObject *)cls)->tp_version_tag != cache1->tp_version,
LOAD_METHOD);
assert(cache1->dk_version_or_hint != 0);
assert(cache1->tp_version != 0);

STAT_INC(LOAD_METHOD, hit);
record_cache_hit(cache0);
PyObject *res = cache2->obj;
assert(res != NULL);
assert(_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR));
Py_INCREF(res);
SET_TOP(NULL);
Py_DECREF(cls);
PUSH(res);
DISPATCH();
}

TARGET(CALL_METHOD): {
/* Designed to work in tamdem with LOAD_METHOD. */
PyObject **sp, *res;
Expand Down Expand Up @@ -4648,6 +4759,7 @@ opname ## _miss: \
MISS_WITH_CACHE(LOAD_ATTR)
MISS_WITH_CACHE(STORE_ATTR)
MISS_WITH_CACHE(LOAD_GLOBAL)
MISS_WITH_CACHE(LOAD_METHOD)
MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)

binary_subscr_dict_error:
Expand Down
24 changes: 12 additions & 12 deletions Python/opcode_targets.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading