Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 8 additions & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ typedef struct {
unsigned int tp_version_tag;
} _PyOpCodeOpt_LoadAttr;

typedef struct {
Py_hash_t hash;
PyTypeObject *type;
PyObject *meth;
unsigned int tp_version_tag;
} _PyOpCodeOpt_LoadMethod;

struct _PyOpcache {
union {
_PyOpcache_LoadGlobal lg;
_PyOpCodeOpt_LoadMethod lm;
_PyOpCodeOpt_LoadAttr la;
} u;
char optimized;
Expand Down
2 changes: 1 addition & 1 deletion Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ _PyCode_InitOpcache(PyCodeObject *co)
i++; // 'i' is now aligned to (next_instr - first_instr)

// TODO: LOAD_METHOD
if (opcode == LOAD_GLOBAL || opcode == LOAD_ATTR) {
if (opcode == LOAD_GLOBAL || opcode == LOAD_ATTR || opcode == LOAD_METHOD) {
opts++;
co->co_opcache_map[i] = (unsigned char)opts;
if (opts > 254) {
Expand Down
159 changes: 156 additions & 3 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ static long dxp[256];
#else
#define OPCACHE_MIN_RUNS 1024 /* create opcache when code executed this time */
#endif
#define OPCODE_CACHE_MAX_TRIES 20
#define OPCACHE_MAX_TRIES 20
Copy link
Member

Choose a reason for hiding this comment

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

Let's maybe add a comment clarifying what this knob does?

#define OPCACHE_STATS 0 /* Enable stats */

#if OPCACHE_STATS
Expand All @@ -127,6 +127,13 @@ static size_t opcache_attr_hits = 0;
static size_t opcache_attr_misses = 0;
static size_t opcache_attr_deopts = 0;
static size_t opcache_attr_total = 0;

static size_t opcache_method_opts = 0;
static size_t opcache_method_hits = 0;
static size_t opcache_method_misses = 0;
static size_t opcache_method_deopts = 0;
static size_t opcache_method_dict_checks = 0;
static size_t opcache_method_total = 0;
#endif


Expand Down Expand Up @@ -396,6 +403,30 @@ _PyEval_Fini(void)

fprintf(stderr, "-- Opcode cache LOAD_ATTR total = %zd\n",
opcache_attr_total);

fprintf(stderr, "\n");

fprintf(stderr, "-- Opcode cache LOAD_METHOD hits = %zd (%d%%)\n",
opcache_method_hits,
(int) (100.0 * opcache_method_hits /
opcache_method_total));

fprintf(stderr, "-- Opcode cache LOAD_METHOD misses = %zd (%d%%)\n",
opcache_method_misses,
(int) (100.0 * opcache_method_misses /
opcache_method_total));

fprintf(stderr, "-- Opcode cache LOAD_METHOD opts = %zd\n",
opcache_method_opts);

fprintf(stderr, "-- Opcode cache LOAD_METHOD deopts = %zd\n",
opcache_method_deopts);

fprintf(stderr, "-- Opcode cache LOAD_METHOD dct-chk= %zd\n",
opcache_method_dict_checks);

fprintf(stderr, "-- Opcode cache LOAD_METHOD total = %zd\n",
opcache_method_total);
#endif
}

Expand Down Expand Up @@ -1358,6 +1389,37 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
if (co->co_opcache != NULL) opcache_attr_total++; \
} while (0)

#define OPCACHE_STAT_METHOD_HIT() \
do { \
if (co->co_opcache != NULL) opcache_method_hits++; \
} while (0)

#define OPCACHE_STAT_METHOD_MISS() \
do { \
if (co->co_opcache != NULL) opcache_method_misses++; \
} while (0)

#define OPCACHE_STAT_METHOD_OPT() \
do { \
if (co->co_opcache != NULL) opcache_method_opts++; \
} while (0)

#define OPCACHE_STAT_METHOD_DEOPT() \
do { \
if (co->co_opcache != NULL) opcache_method_deopts++; \
} while (0)

#define OPCACHE_STAT_METHOD_DICT_CHECK() \
do { \
if (co->co_opcache != NULL) opcache_method_dict_checks++; \
} while (0)

#define OPCACHE_STAT_METHOD_TOTAL() \
do { \
if (co->co_opcache != NULL) opcache_method_total++; \
} while (0)


#else /* OPCACHE_STATS */

#define OPCACHE_STAT_GLOBAL_HIT()
Expand All @@ -1370,6 +1432,13 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
#define OPCACHE_STAT_ATTR_DEOPT()
#define OPCACHE_STAT_ATTR_TOTAL()

#define OPCACHE_STAT_METHOD_HIT()
#define OPCACHE_STAT_METHOD_MISS()
#define OPCACHE_STAT_METHOD_OPT()
#define OPCACHE_STAT_METHOD_DEOPT()
#define OPCACHE_STAT_METHOD_DICT_CHECK()
#define OPCACHE_STAT_METHOD_TOTAL()

#endif

/* Start of code */
Expand Down Expand Up @@ -3239,7 +3308,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
if (co_opcache->optimized == 0) {
// First time we optimize this opcode. */
OPCACHE_STAT_ATTR_OPT();
co_opcache->optimized = OPCODE_CACHE_MAX_TRIES;
co_opcache->optimized = OPCACHE_MAX_TRIES;
}

la = &co_opcache->u.la;
Expand Down Expand Up @@ -3703,9 +3772,82 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)

case TARGET(LOAD_METHOD): {
/* Designed to work in tandem with CALL_METHOD. */
PyObject *name = GETITEM(names, oparg);
PyObject *obj = TOP();
PyObject *meth = NULL;
PyTypeObject *type = Py_TYPE(obj);

OPCACHE_STAT_METHOD_TOTAL();
OPCACHE_CHECK();
if (co_opcache != NULL && co_opcache->optimized > 0) {
_PyOpCodeOpt_LoadMethod *lm = &co_opcache->u.lm;

if (PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG) &&
type->tp_version_tag == lm->tp_version_tag &&
type == lm->type)
{
PyObject **dictptr;
PyObject *dict;
Py_ssize_t dictoffset;

assert(lm->meth != NULL);

dictoffset = type->tp_dictoffset;
if (dictoffset != 0) {
if (dictoffset < 0) {
Py_ssize_t tsize;
size_t size;

tsize = ((PyVarObject *)obj)->ob_size;
if (tsize < 0) {
tsize = -tsize;
}
size = _PyObject_VAR_SIZE(type, tsize);

dictoffset += (long)size;
assert(dictoffset > 0);
assert(dictoffset % SIZEOF_VOID_P == 0);
}
dictptr = (PyObject **) ((char *)obj + dictoffset);
dict = *dictptr;
if (dict != NULL) {
OPCACHE_STAT_METHOD_DICT_CHECK();
Py_INCREF(dict);
PyObject *name = GETITEM(names, oparg);
meth = _PyDict_GetItem_KnownHash(dict, name, lm->hash);
if (meth != NULL) {
OPCACHE_STAT_METHOD_MISS();
OPCACHE_STAT_METHOD_DEOPT();
OPCACHE_DEOPT();

Py_INCREF(meth);
SET_TOP(NULL);
Py_DECREF(obj);
PUSH(meth);
DISPATCH();
} else {
Py_DECREF(dict);
}
}
}

OPCACHE_STAT_METHOD_HIT();
meth = lm->meth;
Py_INCREF(meth);
SET_TOP(meth);
PUSH(obj);
DISPATCH();
} else if (type != lm->type) {
OPCACHE_STAT_METHOD_DEOPT();
OPCACHE_DEOPT();
} else if (--co_opcache->optimized <= 0) {
OPCACHE_STAT_METHOD_DEOPT();
OPCACHE_DEOPT();
}
OPCACHE_STAT_METHOD_MISS();
}


PyObject *name = GETITEM(names, oparg);

int meth_found = _PyObject_GetMethod(obj, name, &meth);

Expand All @@ -3722,6 +3864,17 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
*/
SET_TOP(meth);
PUSH(obj); // self

if (co_opcache != NULL && PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
{
_PyOpCodeOpt_LoadMethod *lm = &co_opcache->u.lm;
co_opcache->optimized = OPCACHE_MAX_TRIES;
lm->type = type;
lm->tp_version_tag = type->tp_version_tag;
lm->meth = meth; /* borrowed */
lm->hash = PyObject_Hash(name);
OPCACHE_STAT_METHOD_OPT();
}
}
else {
/* meth is not an unbound method (but a regular attr, or
Expand Down