You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Use dedicated opcodes to speed up calls/attribute lookups with super() as receiver #87729
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=Nonecreated_at=<Date2021-03-19.20:57:57.938>labels= ['interpreter-core', '3.10']
title='Use dedicated opcodes to speed up calls/attribute lookups with super() as receiver'updated_at=<Date2021-04-27.18:23:03.781>user='https://github.com/vladima'
Calling methods and lookup up attributes when receiver is super() has extra cost comparing to regular attribute lookup. It mainly comes from the need to allocate and initialize the instance of the super which for zero argument case also include peeking into frame/code object for the __class__ cell and first argument. In addition because PySuper_Type has custom implementation of tp_getattro - _PyObject_GetMethod would always return bound method.
import timeit
setup = """
class A:
def f(self): pass
class B(A):
def f(self): super().f()
def g(self): A.f(self)
b = B()
"""
print(timeit.timeit("b.f()", setup=setup, number=20000000))
print(timeit.timeit("b.g()", setup=setup, number=20000000))
7.329449548968114
3.892987059080042
One option to improve it could be to make compiler/interpreter aware of super calls so they can be treated specially. Attached patch introduces two new opcodes LOAD_METHOD_SUPER and LOAD_ATTR_SUPER that are intended to be counterparts for LOAD_METHOD and LOAD_ATTR for cases when receiver is super with either zero or two arguments.
Immediate argument for both LOAD_METHOD_SUPER and LOAD_ATTR_SUPER is a pair that consist of:
0: index of method/attribute in co_names
1: Py_True if super was originally called with 0 arguments and Py_False otherwise.
Both LOAD_METHOD_SUPER and LOAD_ATTR_SUPER expect 3 elements on the stack:
TOS3: global_super
TOS2: type
TOS1: self/cls
Result of LOAD_METHOD_SUPER is the same as LOAD_METHOD.
Result of LOAD_ATTR_SUPER is the same as LOAD_ATTR
In runtime both LOAD_METHOD_SUPER and LOAD_ATTR_SUPER will check if global_super is PySuper_Type to handle situations when super is patched. If global_super is PySuper_Type then it can use dedicated routine to perform the lookup for provided __class__ and cls/self without allocating new super instance. If global_super is different from PySuper_Type then runtime will fallback to the original logic using global_super and original number of arguments that was captured in immediate.
Benchmark results with patch:
4.381768501014449
3.9492998640052974
Do you have any evidence that the overhead of super() is significant in real programs, or that the proposed change actually speeds up anything beyond your micro-benchmark?
Currently, super() is decoupled from the core language. It is just a builtin that provides customized attribute lookup. This PR makes super() more tightly integrated with the core language, treating it as if it were a keyword and part of the grammar. Also note, users can currently create their own versions of super(), shadowing the builtin super().
vladima commented on Mar 22, 2021
vladimamannequin
MannequinAuthor
More actions
Currently, super() is decoupled from the core language. It is just a builtin that provides customized attribute lookup. This PR makes super() more tightly integrated with the core language, treating it as if it were a keyword and part of the grammar. Also note, users can currently create their own versions of super(), shadowing the builtin super().
This is true however:
this patch does not block people from introducing custom version of super so this scenario still work. The idea was to streamline the common case
based on digging into Instagram codebase and its transitive dependencies (which is reasonably large amount of code) all spots where super() appear in sources assume super to be builtin and for a pretty common use-case its cost is noticeable in profiler.
zero-argument super() still a bit magical since it requires compiler support to create cell for __class__ and assumes certain shape of the frame object so this patch is a step forward with a better compiler support and removing runtime dependency on the frame
Do you have any evidence that the overhead of super() is significant in real programs
I do see the non-negligible cost of allocation/initialization of super object in IG profiling data.
This looks like a sensible idea to me. The safeguards to ensure that customized 'super' still works seem reasonable to me. I have to admit that I sometimes refrain from using super() where I should because of the expense, so this would be welcome.
I do wonder -- is two-arg super() important enough to support it at all? Maybe the code would be somewhat simpler if the special opcodes were only generated for zero-arg super() calls.
Activity
vladima commentedon Mar 19, 2021
Calling methods and lookup up attributes when receiver is
super()has extra cost comparing to regular attribute lookup. It mainly comes from the need to allocate and initialize the instance of thesuperwhich for zero argument case also include peeking into frame/code object for the__class__cell and first argument. In addition becausePySuper_Typehas custom implementation of tp_getattro -_PyObject_GetMethodwould always return bound method.One option to improve it could be to make compiler/interpreter aware of super calls so they can be treated specially. Attached patch introduces two new opcodes LOAD_METHOD_SUPER and LOAD_ATTR_SUPER that are intended to be counterparts for LOAD_METHOD and LOAD_ATTR for cases when receiver is super with either zero or two arguments.
Immediate argument for both LOAD_METHOD_SUPER and LOAD_ATTR_SUPER is a pair that consist of:
0: index of method/attribute in co_names
1: Py_True if super was originally called with 0 arguments and Py_False otherwise.
Both LOAD_METHOD_SUPER and LOAD_ATTR_SUPER expect 3 elements on the stack:
TOS3: global_super
TOS2: type
TOS1: self/cls
Result of LOAD_METHOD_SUPER is the same as LOAD_METHOD.
Result of LOAD_ATTR_SUPER is the same as LOAD_ATTR
In runtime both LOAD_METHOD_SUPER and LOAD_ATTR_SUPER will check if
global_superisPySuper_Typeto handle situations whensuperis patched. Ifglobal_superisPySuper_Typethen it can use dedicated routine to perform the lookup for provided__class__andcls/selfwithout allocating newsuperinstance. Ifglobal_superis different fromPySuper_Typethen runtime will fallback to the original logic usingglobal_superand original number of arguments that was captured in immediate.Benchmark results with patch:
4.381768501014449
3.9492998640052974
markshannon commentedon Mar 20, 2021
Why?
Do you have any evidence that the overhead of super() is significant in real programs, or that the proposed change actually speeds up anything beyond your micro-benchmark?
rhettinger commentedon Mar 20, 2021
Currently, super() is decoupled from the core language. It is just a builtin that provides customized attribute lookup. This PR makes super() more tightly integrated with the core language, treating it as if it were a keyword and part of the grammar. Also note, users can currently create their own versions of super(), shadowing the builtin super().
vladima commentedon Mar 22, 2021
This is true however:
superso this scenario still work. The idea was to streamline the common casesuper()appear in sources assumesuperto be builtin and for a pretty common use-case its cost is noticeable in profiler.super()still a bit magical since it requires compiler support to create cell for__class__and assumes certain shape of the frame object so this patch is a step forward with a better compiler support and removing runtime dependency on the frameI do see the non-negligible cost of allocation/initialization of
superobject in IG profiling data.gvanrossum commentedon Mar 22, 2021
This looks like a sensible idea to me. The safeguards to ensure that customized 'super' still works seem reasonable to me. I have to admit that I sometimes refrain from using super() where I should because of the expense, so this would be welcome.
I do wonder -- is two-arg super() important enough to support it at all? Maybe the code would be somewhat simpler if the special opcodes were only generated for zero-arg super() calls.
markshannon commentedon Mar 22, 2021
Numbers please.
What is "non-negligible cost of allocation/initialization" mean as a fraction of runtime?
What sort of speed up are you seeing on whole programs?
vladima commentedon Apr 27, 2021
Apologies for the delay in reply: in more concrete numbers for IG codebase enabling this optimization resulted in 0.2% CPU win.
pythongh-87729: add instruction for faster zero-arg super()
gh-87729: add LOAD_SUPER_ATTR instruction for faster super() (#103497)
Merge branch 'main' into superopt_spec
gh-87729: specialize LOAD_SUPER_ATTR_METHOD (#103809)
pythongh-87729: specialize LOAD_SUPER_ATTR_METHOD (python#103809)
pythongh-87729: improve hit rate of LOAD_SUPER_ATTR specialization
gh-87729: improve hit rate of LOAD_SUPER_ATTR specialization (#104270)
Merge branch 'main' into supermon
pythongh-87729: add LOAD_SUPER_ATTR to 3.12 What's New
gh-87729: add LOAD_SUPER_ATTR to 3.12 What's New (#105125)
pythongh-87729: add LOAD_SUPER_ATTR to 3.12 What's New (pythonGH-105125)
[3.12] gh-87729: add LOAD_SUPER_ATTR to 3.12 What's New (GH-105125) (#…