diff options
author | Carl Meyer <carl@oddbird.net> | 2023-04-24 22:22:14 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-24 22:22:14 (GMT) |
commit | 0dc8b50d33208e9ca4fc3d959c6798529731f020 (patch) | |
tree | 822102a5177183fc6ad7075bd74a02a5633edc52 /Python/compile.c | |
parent | 22bed58e531ce780d91f3364c5ace98fad28c2e8 (diff) | |
download | cpython-0dc8b50d33208e9ca4fc3d959c6798529731f020.zip cpython-0dc8b50d33208e9ca4fc3d959c6798529731f020.tar.gz cpython-0dc8b50d33208e9ca4fc3d959c6798529731f020.tar.bz2 |
gh-87729: add LOAD_SUPER_ATTR instruction for faster super() (#103497)
This speeds up `super()` (by around 85%, for a simple one-level
`super().meth()` microbenchmark) by avoiding allocation of a new
single-use `super()` object on each use.
Diffstat (limited to 'Python/compile.c')
-rw-r--r-- | Python/compile.c | 128 |
1 files changed, 125 insertions, 3 deletions
diff --git a/Python/compile.c b/Python/compile.c index 99ef7c3..a0ad368 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -829,6 +829,10 @@ stack_effect(int opcode, int oparg, int jump) case LOAD_METHOD: return 1; + case LOAD_SUPER_METHOD: + case LOAD_ZERO_SUPER_METHOD: + case LOAD_ZERO_SUPER_ATTR: + return -1; default: return PY_INVALID_STACK_EFFECT; } @@ -1047,6 +1051,24 @@ compiler_addop_name(struct compiler_unit *u, location loc, arg <<= 1; arg |= 1; } + if (opcode == LOAD_SUPER_ATTR) { + arg <<= 2; + arg |= 2; + } + if (opcode == LOAD_SUPER_METHOD) { + opcode = LOAD_SUPER_ATTR; + arg <<= 2; + arg |= 3; + } + if (opcode == LOAD_ZERO_SUPER_ATTR) { + opcode = LOAD_SUPER_ATTR; + arg <<= 2; + } + if (opcode == LOAD_ZERO_SUPER_METHOD) { + opcode = LOAD_SUPER_ATTR; + arg <<= 2; + arg |= 1; + } return codegen_addop_i(&u->u_instr_sequence, opcode, arg, loc); } @@ -4230,6 +4252,89 @@ is_import_originated(struct compiler *c, expr_ty e) return flags & DEF_IMPORT; } +static int +can_optimize_super_call(struct compiler *c, expr_ty attr) +{ + expr_ty e = attr->v.Attribute.value; + if (e->kind != Call_kind || + e->v.Call.func->kind != Name_kind || + !_PyUnicode_EqualToASCIIString(e->v.Call.func->v.Name.id, "super") || + _PyUnicode_EqualToASCIIString(attr->v.Attribute.attr, "__class__") || + asdl_seq_LEN(e->v.Call.keywords) != 0) { + return 0; + } + Py_ssize_t num_args = asdl_seq_LEN(e->v.Call.args); + + PyObject *super_name = e->v.Call.func->v.Name.id; + // detect statically-visible shadowing of 'super' name + int scope = _PyST_GetScope(c->u->u_ste, super_name); + if (scope != GLOBAL_IMPLICIT) { + return 0; + } + scope = _PyST_GetScope(c->c_st->st_top, super_name); + if (scope != 0) { + return 0; + } + + if (num_args == 2) { + for (Py_ssize_t i = 0; i < num_args; i++) { + expr_ty elt = asdl_seq_GET(e->v.Call.args, i); + if (elt->kind == Starred_kind) { + return 0; + } + } + // exactly two non-starred args; we can just load + // the provided args + return 1; + } + + if (num_args != 0) { + return 0; + } + // we need the following for zero-arg super(): + + // enclosing function should have at least one argument + if (c->u->u_metadata.u_argcount == 0 && + c->u->u_metadata.u_posonlyargcount == 0) { + return 0; + } + // __class__ cell should be available + if (get_ref_type(c, &_Py_ID(__class__)) == FREE) { + return 1; + } + return 0; +} + +static int +load_args_for_super(struct compiler *c, expr_ty e) { + location loc = LOC(e); + + // load super() global + PyObject *super_name = e->v.Call.func->v.Name.id; + RETURN_IF_ERROR(compiler_nameop(c, loc, super_name, Load)); + + if (asdl_seq_LEN(e->v.Call.args) == 2) { + VISIT(c, expr, asdl_seq_GET(e->v.Call.args, 0)); + VISIT(c, expr, asdl_seq_GET(e->v.Call.args, 1)); + return SUCCESS; + } + + // load __class__ cell + PyObject *name = &_Py_ID(__class__); + assert(get_ref_type(c, name) == FREE); + RETURN_IF_ERROR(compiler_nameop(c, loc, name, Load)); + + // load self (first argument) + Py_ssize_t i = 0; + PyObject *key, *value; + if (!PyDict_Next(c->u->u_metadata.u_varnames, &i, &key, &value)) { + return ERROR; + } + RETURN_IF_ERROR(compiler_nameop(c, loc, key, Load)); + + return SUCCESS; +} + // If an attribute access spans multiple lines, update the current start // location to point to the attribute name. static location @@ -4297,11 +4402,21 @@ maybe_optimize_method_call(struct compiler *c, expr_ty e) return 0; } } + /* Alright, we can optimize the code. */ - VISIT(c, expr, meth->v.Attribute.value); location loc = LOC(meth); - loc = update_start_location_to_match_attr(c, loc, meth); - ADDOP_NAME(c, loc, LOAD_METHOD, meth->v.Attribute.attr, names); + + if (can_optimize_super_call(c, meth)) { + RETURN_IF_ERROR(load_args_for_super(c, meth->v.Attribute.value)); + int opcode = asdl_seq_LEN(meth->v.Attribute.value->v.Call.args) ? + LOAD_SUPER_METHOD : LOAD_ZERO_SUPER_METHOD; + ADDOP_NAME(c, loc, opcode, meth->v.Attribute.attr, names); + } else { + VISIT(c, expr, meth->v.Attribute.value); + loc = update_start_location_to_match_attr(c, loc, meth); + ADDOP_NAME(c, loc, LOAD_METHOD, meth->v.Attribute.attr, names); + } + VISIT_SEQ(c, expr, e->v.Call.args); if (kwdsl) { @@ -5309,6 +5424,13 @@ compiler_visit_expr1(struct compiler *c, expr_ty e) return compiler_formatted_value(c, e); /* The following exprs can be assignment targets. */ case Attribute_kind: + if (e->v.Attribute.ctx == Load && can_optimize_super_call(c, e)) { + RETURN_IF_ERROR(load_args_for_super(c, e->v.Attribute.value)); + int opcode = asdl_seq_LEN(e->v.Attribute.value->v.Call.args) ? + LOAD_SUPER_ATTR : LOAD_ZERO_SUPER_ATTR; + ADDOP_NAME(c, loc, opcode, e->v.Attribute.attr, names); + return SUCCESS; + } VISIT(c, expr, e->v.Attribute.value); loc = LOC(e); loc = update_start_location_to_match_attr(c, loc, e); |