mirror of
https://github.com/python/cpython.git
synced 2025-08-23 10:16:01 +00:00
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.
This commit is contained in:
parent
22bed58e53
commit
0dc8b50d33
18 changed files with 783 additions and 408 deletions
128
Python/compile.c
128
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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue