Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Specialize LOAD_ATTR for module attributes.
  • Loading branch information
markshannon committed Jun 8, 2021
commit 85dd177788d96dc28a229d28695f79e9626c2a3c
19 changes: 19 additions & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,25 @@ cache_backoff(_PyAdaptiveEntry *entry) {

int _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);

#define SPECIALIZATION_STATS 0
#if SPECIALIZATION_STATS

typedef struct _specialization_stats {
uint64_t specialization_success;
uint64_t specialization_failure;
uint64_t loadattr_hit;
uint64_t loadattr_deferred;
uint64_t loadattr_miss;
uint64_t loadattr_deopt;
} SpecializationStats;

extern SpecializationStats _specialization_stats;
#define STAT_INC(name) _specialization_stats.name++
void _Py_PrintSpecializationStats(void);
#else
#define STAT_INC(name) ((void)0)
#endif


#ifdef __cplusplus
}
Expand Down
1 change: 1 addition & 0 deletions Include/opcode.h

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

1 change: 1 addition & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,5 @@ def jabs_op(name, op):
"LOAD_ATTR_SPLIT_KEYS",
"LOAD_ATTR_COMBINED_KEYS",
"LOAD_ATTR_SLOT",
"LOAD_ATTR_MODULE",
]
22 changes: 22 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "pycore_code.h" // _PyCode_InitOpcache()
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_object.h" // _PyObject_GC_TRACK()
#include "pycore_moduleobject.h"
#include "pycore_pyerrors.h" // _PyErr_Fetch()
#include "pycore_pylifecycle.h" // _PyErr_Print()
#include "pycore_pymem.h" // _PyMem_IsPtrFreed()
Expand Down Expand Up @@ -3475,6 +3476,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
DISPATCH();
}
else {
STAT_INC(loadattr_deferred);
cache->adaptive.counter--;
oparg = cache->adaptive.original_oparg;
JUMP_TO_INSTRUCTION(LOAD_ATTR);
Expand All @@ -3498,6 +3500,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
res = dict->ma_values[cache0->index];
DEOPT_IF(res == NULL, LOAD_ATTR);
record_cache_hit(cache0);
STAT_INC(loadattr_hit);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just casually reading through some of the code, it looks like the STAT_INC(loadattr_hit); is duplicated here (see two lines up).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Carl. #26718

Py_INCREF(res);
SET_TOP(res);
Py_DECREF(owner);
DISPATCH();
}

case TARGET(LOAD_ATTR_MODULE): {
PyObject *owner = TOP();
PyObject *res;
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyLoadAttrCache *cache1 = &caches[-1].load_attr;
DEOPT_IF(!PyModule_CheckExact(owner), LOAD_ATTR);
PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict;
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, LOAD_ATTR);
PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + cache0->index;
res = ep->me_value;
DEOPT_IF(res == NULL, LOAD_ATTR);
record_cache_hit(cache0);
Py_INCREF(res);
SET_TOP(res);
Py_DECREF(owner);
Expand Down
2 changes: 1 addition & 1 deletion Python/opcode_targets.h

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

65 changes: 63 additions & 2 deletions Python/specialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "Python.h"
#include "pycore_code.h"
#include "pycore_dict.h"
#include "pycore_moduleobject.h"
#include "opcode.h"
#include "structmember.h" // struct PyMemberDef, T_OFFSET_EX

Expand Down Expand Up @@ -31,6 +32,22 @@
*/

Py_ssize_t _Py_QuickenedCount = 0;
#if SPECIALIZATION_STATS
SpecializationStats _specialization_stats = { 0 };

#define PRINT_STAT(name) fprintf(stderr, #name " : %" PRIu64" \n", _specialization_stats.name);
void
_Py_PrintSpecializationStats(void)
{
PRINT_STAT(specialization_success);
PRINT_STAT(specialization_failure);
PRINT_STAT(loadattr_hit);
PRINT_STAT(loadattr_deferred);
PRINT_STAT(loadattr_miss);
PRINT_STAT(loadattr_deopt);
}

#endif

static SpecializedCacheOrInstruction *
allocate(int cache_count, int instruction_count)
Expand Down Expand Up @@ -204,11 +221,55 @@ _Py_Quicken(PyCodeObject *code) {
return 0;
}

int
special_module_load_attr(
PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
_PyAdaptiveEntry *cache0, _PyLoadAttrCache *cache1)
{
PyModuleObject *m = (PyModuleObject *)owner;
PyObject *attr, *getattr;
_Py_IDENTIFIER(__getattr__);
PyDictObject *dict = (PyDictObject *)m->md_dict;
getattr = _PyDict_GetItemIdWithError(m->md_dict, &PyId___getattr__);
if (PyErr_Occurred()) {
PyErr_Clear();
return -1;
}
if (getattr != NULL) {
return -1;
}
Py_hash_t hash = PyObject_Hash(name);
if (hash == -1) {
PyErr_Clear();
return -1;
}
Py_ssize_t index = _Py_dict_lookup(dict, name, hash, &attr);
assert (index != DKIX_ERROR);
if (index != (uint16_t)index) {
return -1;
}
uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(dict);
if (keys_version == 0) {
return -1;
}
cache1->dk_version_or_hint = keys_version;
cache0->index = index;
*instr = _Py_MAKECODEUNIT(LOAD_ATTR_MODULE, _Py_OPARG(*instr));
return 0;
}

int
_Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache)
{
_PyAdaptiveEntry *cache0 = &cache->adaptive;
_PyLoadAttrCache *cache1 = &cache[-1].load_attr;
if (PyModule_CheckExact(owner)) {
int err = special_module_load_attr(owner, instr, name, cache0, cache1);
if (err) {
goto fail;
}
goto success;
}
PyTypeObject *type = Py_TYPE(owner);
if (type->tp_getattro != PyObject_GenericGetAttr) {
goto fail;
Expand Down Expand Up @@ -254,7 +315,7 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
&& dict->ma_keys == ((PyHeapTypeObject*)type)->ht_cached_keys
) {
// Keys are shared
assert(PyUnicode_CheckExact());
assert(PyUnicode_CheckExact(name));
Py_hash_t hash = PyObject_Hash(name);
if (hash == -1) {
return -1;
Expand All @@ -276,7 +337,7 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
goto success;
}
else {
PyObject *value;
PyObject *value = NULL;
Py_ssize_t hint =
_PyDict_GetItemHint(dict, name, -1, &value);
if (hint != (uint32_t)hint) {
Expand Down