diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h
index f1e89d96b9..342e03bd39 100644
--- a/Include/internal/pycore_code.h
+++ b/Include/internal/pycore_code.h
@@ -16,9 +16,16 @@ typedef struct {
unsigned int tp_version_tag;
} _PyOpCodeOpt_LoadAttr;
+typedef struct {
+ 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;
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index f7613e8fd2..dcbe72ef43 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -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) {
diff --git a/Python/ceval.c b/Python/ceval.c
index 3aa2aa2c9b..03d8f18ce4 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -115,7 +115,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
#define OPCACHE_STATS 0 /* Enable stats */
#if OPCACHE_STATS
@@ -131,6 +131,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
@@ -400,6 +407,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
}
@@ -1362,6 +1393,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()
@@ -1374,6 +1436,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 */
@@ -3262,7 +3331,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;
// fprintf(stderr, "Setting hint for %s, offset %zd\n", dmem->name, offset);
}
@@ -3277,7 +3346,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
Py_INCREF(res);
Py_DECREF(owner);
SET_TOP(res);
+ if (co_opcache->optimized == 0) {
+ // First time we optimize this opcode. */
+ OPCACHE_STAT_ATTR_OPT();
+ co_opcache->optimized = OPCACHE_MAX_TRIES;
+ }
+ la = &co_opcache->u.la;
+ la->type = type;
+ la->tp_version_tag = type->tp_version_tag;
DISPATCH();
}
// Else slot is NULL. Fall through to slow path to raise AttributeError(name).
@@ -3304,7 +3381,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;
@@ -3764,9 +3841,39 @@ _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)
+ {
+ assert(lm->meth != NULL);
+ assert(type->tp_dictoffset == 0);
+ 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);
@@ -3783,6 +3890,18 @@ _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) &&
+ type->tp_dictoffset == 0)
+ {
+ _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 */
+ OPCACHE_STAT_METHOD_OPT();
+ }
}
else {
/* meth is not an unbound method (but a regular attr, or