← 返回首页
bpo-46564: Optimize ``super().meth()`` calls via adaptive superinstructions by Fidget-Spinner · Pull Request #30992 · python/cpython · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus

bpo-46564: Optimize super().meth() calls via adaptive superinstructions#30992

Closed
Fidget-Spinner wants to merge 11 commits into
python:mainfrom
Fidget-Spinner:zero_cost_super
Closed

bpo-46564: Optimize super().meth() calls via adaptive superinstructions#30992
Fidget-Spinner wants to merge 11 commits into
python:mainfrom
Fidget-Spinner:zero_cost_super

Conversation

Fidget-Spinner commented Jan 28, 2022
edited
Loading

Copy link
Copy Markdown
Member

They should now have almost no overhead over a corresponding self.meth() call.

Summary of changes:

  • typeobject.c -- refactoring to reuse code during specialization, also use InterpreterFrame over PyFrameObject for lazy frame benefits. Some changes here are partially taken from bpo-43563 : Introduce dedicated opcodes for super calls #24936. All credits to @vladima (I've tried to properly include them in the news item too.)
  • specialize.c -- specialize for the 0-argument and 2-argument form of super().
  • ceval.c -- does both a CALL and LOAD_METHOD without intermediates (and both are specialized forms too).

TODO:
benchmarks!

https://bugs.python.org/issue46564

Fidget-Spinner changed the title bpo-46564: Optimize super().meth() calls bpo-46564: Optimize super().meth() calls via adaptive superinstructions Jan 28, 2022
markshannon self-assigned this Jan 28, 2022
Fidget-Spinner marked this pull request as draft January 28, 2022 18:14

Copy link
Copy Markdown
Member Author

Marking as draft as I need make this work with the new CALL convention.

Comment thread Python/ceval.c Show resolved Hide resolved
Comment thread Python/ceval.c

DEOPT_IF(_PyType_CAST(super_callable) != &PySuper_Type, CALL);
/* super() - zero argument form */
if (_PySuper_GetTypeArgs(frame, frame->f_code, &su_type, &su_obj) < 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment

Can't we do this at specialization time? The number of locals, the index of "self", and whether it is a cell are all known then. Likewise the nature of __class__ is also known.

Comment thread Python/ceval.c
}
assert(su_obj != NULL);
DEOPT_IF(lm_adaptive->version != Py_TYPE(su_obj)->tp_version_tag, CALL);
DEOPT_IF(cache0->version != su_type->tp_version_tag, CALL);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment

When can this fail?
Isn't the next item in the MRO determined solely by type(self) and __class__, both of which are known at this point?

Fidget-Spinner Jan 29, 2022
edited
Loading

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment

I wanted assurance that __class__ didn't change.. Then again, I'm not sure if it can?

Copy link
Copy Markdown
Member

Maybe we should merge #31002 first, as that PR is simpler.
It would also allow us to compare the performance of just the specialization, without the removal of frame object allocation.

Copy link
Copy Markdown
Member Author

Maybe we should merge #31002 first, as that PR is simpler. It would also allow us to compare the performance of just the specialization, without the removal of frame object allocation.

👍

Copy link
Copy Markdown
Member Author

Mark, I'm going to run benchmarks on deltablue first since it uses 2-argument form super. I'll address your optimization ideas for the 0-arg form once those results come back. Fingers crossed.

arhadthedev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment

A couple of indentation-related inconsistencies:

Comment thread Objects/typeobject.c
super_init_without_args(InterpreterFrame *cframe, PyCodeObject *co,
int
_PySuper_GetTypeArgs(InterpreterFrame *cframe, PyCodeObject *co,
PyTypeObject **type_p, PyObject **obj_p)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment
Suggested change
PyTypeObject **type_p, PyObject **obj_p)
PyTypeObject **type_p, PyObject **obj_p)

The line was aligned with an opening parenthesis of a parameter list.

Comment on lines +278 to +279
PyObject *kwnames, SpecializedCacheEntry *cache, PyObject *builtins,
PyObject **stack_pointer, InterpreterFrame *frame, PyObject *names);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment
Suggested change
PyObject *kwnames, SpecializedCacheEntry *cache, PyObject *builtins,
PyObject **stack_pointer, InterpreterFrame *frame, PyObject *names);
PyObject *kwnames, SpecializedCacheEntry *cache, PyObject *builtins,
PyObject **stack_pointer, InterpreterFrame *frame, PyObject *names);

as in a removed line, or even:

Suggested change
PyObject *kwnames, SpecializedCacheEntry *cache, PyObject *builtins,
PyObject **stack_pointer, InterpreterFrame *frame, PyObject *names);
PyObject *kwnames, SpecializedCacheEntry *cache, PyObject *builtins,
PyObject **stack_pointer, InterpreterFrame *frame, PyObject *names);

as in _Py_Specialize_BinaryOp right below.

Copy link
Copy Markdown
Member Author

Mark, I'm going to run benchmarks on deltablue first since it uses 2-argument form super. I'll address your optimization ideas for the 0-arg form once those results come back. Fingers crossed.

Well that was depressing. deltablue only shows 1.03x speedup. Looking closer at the code, super isn't called in any tight loops so that might be why. Maybe I need to pull out microbenchmarks now.

Fidget-Spinner commented Feb 1, 2022
edited
Loading

Copy link
Copy Markdown
Member Author

Microbenchmarks show that super() has sped up by more than 2.2x. This is faster than that other attempt because there's also speedups from the LOAD_METHOD_CACHED:
(Extremely unscientific, I'm short on time to set up pyperf right now)

import timeit setup = """ class A: def f(self): pass class B(A): def g(self): super().f() def h(self): self.f() b = B() """ # super() call print(timeit.timeit("b.g()", setup=setup, number=20_000_000)) # reference print(timeit.timeit("b.h()", setup=setup, number=20_000_000))

Results:

# Main 5.796037399995839 2.4094066999969073 # This branch 2.4578273000006448 2.3718886000060593

So super().meth() is now only ~10% slowly than the corresponding self.meth() call whereas it was nearly 2x as slow previously. If I manage to incorporate your suggestions correctly, this will effectively just be a competition between LOAD_GLOBAL_BUILTIN (super) and LOAD_FAST (self).

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting core review performance Performance or resource usage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

Footer

© 2026 GitHub, Inc.