GeezerVison is showing github.com — Optimise `inspect.getattr_static` · Issue #103193 · python/cpython
Optimise inspect.getattr_static #103193DescriptionFeature or enhancement
Following 6d59c9e, typing._ProtocolMeta.__instancecheck__ uses inspect.getattr_static in a tight loop, where it had previously used hasattr. This improves semantics in several highly desirable ways, but causes a considerable slowdown for _ProtocolMeta.__instancecheck__, as inspect.getattr_static is much slower than hasattr.
The performance hit to _ProtocolMeta.__instancecheck__ has already been mostly mitigated through several typing-specific optimisations that are tracked in this issue:
However, it would be good to also see if we can improve the performance of inspect.getattr_static. This will not only improve the performance of isinstance() checks against classes subclassing typing.Protocol. It will also improve the performance of all other tools that use inspect.getattr_static for introspection without side effects.
Linked PRs
Activityadded 2 commits that reference this issue on Apr 2, 2023gh-103193: Micro-optimise helper functions for `inspect.getattr_stati… AlexWaygood commented on Apr 5, 2023 I can't see anything further that can be easily optimised here, but happy to reopen if somebody else can! sobolevn commented on Apr 5, 2023 I've measured my ideas, but the results are close to noise.
Initial numbers:
type[Foo] : 793 ± 1 ns
Foo : 1852 ± 3 ns
type[Bar] : 791 ± 1 ns
Bar : 1843 ± 2 ns
WithParentClassX : 2768 ± 2 ns
Baz : 2712 ± 8 ns
WithParentX : 3611 ± 9 ns
type[Missing] : 3364 ± 13 ns
Missing : 2749 ± 22 ns
Slotted : 2363 ± 7 ns
Method : 1863 ± 11 ns
StMethod : 1863 ± 8 ns
ClsMethod : 1847 ± 4 ns
After all changes:
type[Foo] : 793 ± 2 ns
Foo : 1833 ± 2 ns
type[Bar] : 793 ± 3 ns
Bar : 1833 ± 5 ns
WithParentClassX : 2756 ± 4 ns
Baz : 2701 ± 8 ns
WithParentX : 3696 ± 28 ns
type[Missing] : 3390 ± 41 ns
Missing : 2703 ± 9 ns
Slotted : 2312 ± 11 ns
Method : 1860 ± 19 ns
StMethod : 1828 ± 5 ns
ClsMethod : 1859 ± 23 ns
Code:
from timeit import timeit
from statistics import mean, stdev
from inspect import getattr_static
class Foo:
@property
def x(self) -> int:
return 42
class Bar:
x = 42
class WithParentClassX(Bar): ...
class Baz:
def __init__(self):
self.x = 42
class WithParentX(Baz): ...
class Missing: ...
class Slotted:
__slots__ = ('x',)
def __init__(self):
self.x = 42
class Method:
def x(self): ...
class ClsMethod:
@classmethod
def x(cls): ...
class StMethod:
@staticmethod
def x(): ...
import gc
gc.disable()
times = []
def stats():
ts = [t * 1e8 for t in sorted(times)[:5]]
return f'{round(mean(ts)):4} ± {round(stdev(ts)):2} ns '
def bench(obj):
# Warmup:
for _ in range(5):
number = 100
timeit(lambda: getattr_static(obj, 'x', None), number=number)
# Actual bench:
for _ in range(50):
number = 1000
t = timeit(lambda: getattr_static(obj, 'x', None), number=number) / number
times.append(t)
bench_name = (
f'type[{obj.__name__}]'
if isinstance(obj, type)
else obj.__class__.__name__
)
print(f"{bench_name: <25}: {stats()}")
times.clear()
bench(Foo)
bench(Foo())
bench(Bar)
bench(Bar())
bench(WithParentClassX())
bench(Baz())
bench(WithParentX())
bench(Missing)
bench(Missing())
bench(Slotted())
bench(Method())
bench(StMethod())
bench(ClsMethod())added 4 commits that reference this issue on Apr 6, 2023gh-103193: Use LBYL idioms rather than EAFP in `inspect.getattr_stati… AlexWaygood commented on Apr 6, 2023 I think I'm once again out of ideas, at least for now. namka279 commented on Jul 21, 2024
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|