aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPablo Galindo <Pablogsal@gmail.com>2020-10-20 06:22:44 +0100
committerGitHub <noreply@github.com>2020-10-20 06:22:44 +0100
commit109826c8508dd02e06ae0f1784f1d202495a8680 (patch)
tree75b1dd3041d325b379cf50d2ebc5f6ce968fc151 /Python/ceval.c
parentbpo-4356: Add key function support to the bisect module (GH-20556) (diff)
downloadcpython-109826c8508dd02e06ae0f1784f1d202495a8680.tar.gz
cpython-109826c8508dd02e06ae0f1784f1d202495a8680.tar.bz2
cpython-109826c8508dd02e06ae0f1784f1d202495a8680.zip
bpo-42093: Add opcode cache for LOAD_ATTR (GH-22803)
Diffstat (limited to 'Python/ceval.c')
-rw-r--r--Python/ceval.c221
1 files changed, 216 insertions, 5 deletions
diff --git a/Python/ceval.c b/Python/ceval.c
index 762de577e6b..fafbf7524bb 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -111,6 +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_STATS 0 /* Enable stats */
#if OPCACHE_STATS
@@ -120,6 +121,12 @@ static size_t opcache_code_objects_extra_mem = 0;
static size_t opcache_global_opts = 0;
static size_t opcache_global_hits = 0;
static size_t opcache_global_misses = 0;
+
+static size_t opcache_attr_opts = 0;
+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;
#endif
@@ -365,6 +372,25 @@ _PyEval_Fini(void)
opcache_global_opts);
fprintf(stderr, "\n");
+
+ fprintf(stderr, "-- Opcode cache LOAD_ATTR hits = %zd (%d%%)\n",
+ opcache_attr_hits,
+ (int) (100.0 * opcache_attr_hits /
+ opcache_attr_total));
+
+ fprintf(stderr, "-- Opcode cache LOAD_ATTR misses = %zd (%d%%)\n",
+ opcache_attr_misses,
+ (int) (100.0 * opcache_attr_misses /
+ opcache_attr_total));
+
+ fprintf(stderr, "-- Opcode cache LOAD_ATTR opts = %zd\n",
+ opcache_attr_opts);
+
+ fprintf(stderr, "-- Opcode cache LOAD_ATTR deopts = %zd\n",
+ opcache_attr_deopts);
+
+ fprintf(stderr, "-- Opcode cache LOAD_ATTR total = %zd\n",
+ opcache_attr_total);
#endif
}
@@ -1224,16 +1250,43 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
do { \
co_opcache = NULL; \
if (co->co_opcache != NULL) { \
- unsigned char co_opt_offset = \
+ unsigned char co_opcache_offset = \
co->co_opcache_map[next_instr - first_instr]; \
- if (co_opt_offset > 0) { \
- assert(co_opt_offset <= co->co_opcache_size); \
- co_opcache = &co->co_opcache[co_opt_offset - 1]; \
+ if (co_opcache_offset > 0) { \
+ assert(co_opcache_offset <= co->co_opcache_size); \
+ co_opcache = &co->co_opcache[co_opcache_offset - 1]; \
assert(co_opcache != NULL); \
} \
} \
} while (0)
+#define OPCACHE_DEOPT() \
+ do { \
+ if (co_opcache != NULL) { \
+ co_opcache->optimized = -1; \
+ unsigned char co_opcache_offset = \
+ co->co_opcache_map[next_instr - first_instr]; \
+ assert(co_opcache_offset <= co->co_opcache_size); \
+ co->co_opcache_map[co_opcache_offset] = 0; \
+ co_opcache = NULL; \
+ } \
+ } while (0)
+
+#define OPCACHE_DEOPT_LOAD_ATTR() \
+ do { \
+ if (co_opcache != NULL) { \
+ OPCACHE_STAT_ATTR_DEOPT(); \
+ OPCACHE_DEOPT(); \
+ } \
+ } while (0)
+
+#define OPCACHE_MAYBE_DEOPT_LOAD_ATTR() \
+ do { \
+ if (co_opcache != NULL && --co_opcache->optimized <= 0) { \
+ OPCACHE_DEOPT_LOAD_ATTR(); \
+ } \
+ } while (0)
+
#if OPCACHE_STATS
#define OPCACHE_STAT_GLOBAL_HIT() \
@@ -1251,12 +1304,43 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
if (co->co_opcache != NULL) opcache_global_opts++; \
} while (0)
+#define OPCACHE_STAT_ATTR_HIT() \
+ do { \
+ if (co->co_opcache != NULL) opcache_attr_hits++; \
+ } while (0)
+
+#define OPCACHE_STAT_ATTR_MISS() \
+ do { \
+ if (co->co_opcache != NULL) opcache_attr_misses++; \
+ } while (0)
+
+#define OPCACHE_STAT_ATTR_OPT() \
+ do { \
+ if (co->co_opcache!= NULL) opcache_attr_opts++; \
+ } while (0)
+
+#define OPCACHE_STAT_ATTR_DEOPT() \
+ do { \
+ if (co->co_opcache != NULL) opcache_attr_deopts++; \
+ } while (0)
+
+#define OPCACHE_STAT_ATTR_TOTAL() \
+ do { \
+ if (co->co_opcache != NULL) opcache_attr_total++; \
+ } while (0)
+
#else /* OPCACHE_STATS */
#define OPCACHE_STAT_GLOBAL_HIT()
#define OPCACHE_STAT_GLOBAL_MISS()
#define OPCACHE_STAT_GLOBAL_OPT()
+#define OPCACHE_STAT_ATTR_HIT()
+#define OPCACHE_STAT_ATTR_MISS()
+#define OPCACHE_STAT_ATTR_OPT()
+#define OPCACHE_STAT_ATTR_DEOPT()
+#define OPCACHE_STAT_ATTR_TOTAL()
+
#endif
/* Start of code */
@@ -3023,7 +3107,134 @@ main_loop:
case TARGET(LOAD_ATTR): {
PyObject *name = GETITEM(names, oparg);
PyObject *owner = TOP();
- PyObject *res = PyObject_GetAttr(owner, name);
+
+ PyTypeObject *type = Py_TYPE(owner);
+ PyObject *res;
+ PyObject **dictptr;
+ PyObject *dict;
+ _PyOpCodeOpt_LoadAttr *la;
+
+ OPCACHE_STAT_ATTR_TOTAL();
+
+ OPCACHE_CHECK();
+ if (co_opcache != NULL && PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG))
+ {
+ if (co_opcache->optimized > 0) {
+ /* Fast path -- cache hit makes LOAD_ATTR ~30% faster */
+ la = &co_opcache->u.la;
+ if (la->type == type && la->tp_version_tag == type->tp_version_tag)
+ {
+ assert(type->tp_dict != NULL);
+ assert(type->tp_dictoffset > 0);
+
+ dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
+ dict = *dictptr;
+ if (dict != NULL && PyDict_CheckExact(dict)) {
+ Py_ssize_t hint = la->hint;
+ Py_INCREF(dict);
+ res = NULL;
+ la->hint = _PyDict_GetItemHint((PyDictObject*)dict, name, hint, &res);
+
+ if (res != NULL) {
+ if (la->hint == hint && hint >= 0) {
+ /* Our hint has helped -- cache hit. */
+ OPCACHE_STAT_ATTR_HIT();
+ } else {
+ /* The hint we provided didn't work.
+ Maybe next time? */
+ OPCACHE_MAYBE_DEOPT_LOAD_ATTR();
+ }
+
+ Py_INCREF(res);
+ SET_TOP(res);
+ Py_DECREF(owner);
+ Py_DECREF(dict);
+ DISPATCH();
+ } else {
+ // This attribute can be missing sometimes -- we
+ // don't want to optimize this lookup.
+ OPCACHE_DEOPT_LOAD_ATTR();
+ Py_DECREF(dict);
+ }
+ } else {
+ // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact
+ OPCACHE_DEOPT_LOAD_ATTR();
+ }
+ } else {
+ // The type of the object has either been updated,
+ // or is different. Maybe it will stabilize?
+ OPCACHE_MAYBE_DEOPT_LOAD_ATTR();
+ }
+
+ OPCACHE_STAT_ATTR_MISS();
+ }
+
+ if (co_opcache != NULL && /* co_opcache can be NULL after a DEOPT() call. */
+ type->tp_getattro == PyObject_GenericGetAttr)
+ {
+ PyObject *descr;
+ Py_ssize_t ret;
+
+ if (type->tp_dictoffset > 0) {
+ if (type->tp_dict == NULL) {
+ if (PyType_Ready(type) < 0) {
+ Py_DECREF(owner);
+ SET_TOP(NULL);
+ goto error;
+ }
+ }
+
+ descr = _PyType_Lookup(type, name);
+ if (descr == NULL ||
+ descr->ob_type->tp_descr_get == NULL ||
+ !PyDescr_IsData(descr))
+ {
+ dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
+ dict = *dictptr;
+
+ if (dict != NULL && PyDict_CheckExact(dict)) {
+ Py_INCREF(dict);
+ res = NULL;
+ ret = _PyDict_GetItemHint((PyDictObject*)dict, name, -1, &res);
+ if (res != NULL) {
+ Py_INCREF(res);
+ Py_DECREF(dict);
+ Py_DECREF(owner);
+ SET_TOP(res);
+
+ if (co_opcache->optimized == 0) {
+ // First time we optimize this opcode. */
+ OPCACHE_STAT_ATTR_OPT();
+ co_opcache->optimized = OPCODE_CACHE_MAX_TRIES;
+ }
+
+ la = &co_opcache->u.la;
+ la->type = type;
+ la->tp_version_tag = type->tp_version_tag;
+ la->hint = ret;
+
+ DISPATCH();
+ }
+ Py_DECREF(dict);
+ } else {
+ // There is no dict, or __dict__ doesn't satisfy PyDict_CheckExact
+ OPCACHE_DEOPT_LOAD_ATTR();
+ }
+ } else {
+ // We failed to find an attribute without a data-like descriptor
+ OPCACHE_DEOPT_LOAD_ATTR();
+ }
+ } else {
+ // The object's class does not have a tp_dictoffset we can use
+ OPCACHE_DEOPT_LOAD_ATTR();
+ }
+ } else if (type->tp_getattro != PyObject_GenericGetAttr) {
+ OPCACHE_DEOPT_LOAD_ATTR();
+ }
+ }
+
+ /* slow path */
+ res = PyObject_GetAttr(owner, name);
Py_DECREF(owner);
SET_TOP(res);
if (res == NULL)