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