bpo-46564: Optimize super().meth() calls via adaptive superinstructions#30992
bpo-46564: Optimize super().meth() calls via adaptive superinstructions#30992Fidget-Spinner wants to merge 11 commits intopython:mainfrom
super().meth() calls via adaptive superinstructions#30992Conversation
super().meth() callssuper().meth() calls via adaptive superinstructions
Co-Authored-By: Vladimir Matveev <v2matveev@outlook.com>
|
Marking as draft as I need make this work with the new |
|
|
||
| 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) { |
There was a problem hiding this 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.
| } | ||
| 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); |
There was a problem hiding this 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?
There was a problem hiding this comment.
I wanted assurance that __class__ didn't change.. Then again, I'm not sure if it can?
|
Maybe we should merge #31002 first, as that PR is simpler. |
👍 |
|
Mark, I'm going to run benchmarks on |
arhadthedev
left a comment
There was a problem hiding this comment.
A couple of indentation-related inconsistencies:
| super_init_without_args(InterpreterFrame *cframe, PyCodeObject *co, | ||
| int | ||
| _PySuper_GetTypeArgs(InterpreterFrame *cframe, PyCodeObject *co, | ||
| PyTypeObject **type_p, PyObject **obj_p) |
There was a problem hiding this comment.
| PyTypeObject **type_p, PyObject **obj_p) | |
| PyTypeObject **type_p, PyObject **obj_p) |
The line was aligned with an opening parenthesis of a parameter list.
| PyObject *kwnames, SpecializedCacheEntry *cache, PyObject *builtins, | ||
| PyObject **stack_pointer, InterpreterFrame *frame, PyObject *names); |
There was a problem hiding this comment.
| 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:
| 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.
Well that was depressing. |
|
Microbenchmarks show that 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: So |
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 useInterpreterFrameoverPyFrameObjectfor 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 ofsuper().ceval.c-- does both aCALLandLOAD_METHODwithout intermediates (and both are specialized forms too).TODO:
benchmarks!
https://bugs.python.org/issue46564