Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimise inspect.getattr_static #103193

Closed
AlexWaygood opened this issue Apr 2, 2023 · 4 comments
Closed

Optimise inspect.getattr_static #103193

AlexWaygood opened this issue Apr 2, 2023 · 4 comments
Assignees
Labels
3.12 bugs and security fixes performance Performance or resource usage stdlib Python modules in the Lib dir type-feature A feature request or enhancement

Comments

@AlexWaygood
Copy link
Member

AlexWaygood commented Apr 2, 2023

Feature 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

@AlexWaygood AlexWaygood added type-feature A feature request or enhancement performance Performance or resource usage stdlib Python modules in the Lib dir 3.12 bugs and security fixes labels Apr 2, 2023
AlexWaygood added a commit to AlexWaygood/cpython that referenced this issue Apr 2, 2023
@AlexWaygood
Copy link
Member Author

I can't see anything further that can be easily optimised here, but happy to reopen if somebody else can!

@sobolevn
Copy link
Member

sobolevn commented 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())

@AlexWaygood
Copy link
Member Author

I think I'm once again out of ideas, at least for now.

AlexWaygood added a commit to AlexWaygood/cpython that referenced this issue Apr 7, 2023
AlexWaygood added a commit that referenced this issue Apr 7, 2023
gaogaotiantian pushed a commit to gaogaotiantian/cpython that referenced this issue Apr 8, 2023
warsaw pushed a commit to warsaw/cpython that referenced this issue Apr 11, 2023
warsaw pushed a commit to warsaw/cpython that referenced this issue Apr 11, 2023
warsaw pushed a commit to warsaw/cpython that referenced this issue Apr 11, 2023
Improve performance of `inspect.getattr_static`
warsaw pushed a commit to warsaw/cpython that referenced this issue Apr 11, 2023
AlexWaygood added a commit to AlexWaygood/cpython that referenced this issue May 7, 2023
AlexWaygood added a commit that referenced this issue May 7, 2023
AlexWaygood added a commit to AlexWaygood/cpython that referenced this issue May 8, 2023
miss-islington pushed a commit to miss-islington/cpython that referenced this issue May 8, 2023
)

(cherry picked from commit 921185e)

Co-authored-by: Alex Waygood <[email protected]>
AlexWaygood added a commit that referenced this issue May 8, 2023
…104290)

gh-103193: Improve `getattr_static` test coverage (GH-104286)
(cherry picked from commit 921185e)

Co-authored-by: Alex Waygood <[email protected]>
jbower-fb pushed a commit to jbower-fb/cpython-jbowerfb that referenced this issue May 8, 2023
jbower-fb pushed a commit to jbower-fb/cpython-jbowerfb that referenced this issue May 8, 2023
carljm added a commit to carljm/cpython that referenced this issue May 9, 2023
* main: (47 commits)
  pythongh-97696 Remove unnecessary check for eager_start kwarg (python#104188)
  pythonGH-104308: socket.getnameinfo should release the GIL (python#104307)
  pythongh-104310: Add importlib.util.allowing_all_extensions() (pythongh-104311)
  pythongh-99113: A Per-Interpreter GIL! (pythongh-104210)
  pythonGH-104284: Fix documentation gettext build (python#104296)
  pythongh-89550: Buffer GzipFile.write to reduce execution time by ~15% (python#101251)
  pythongh-104223: Fix issues with inheriting from buffer classes (python#104227)
  pythongh-99108: fix typo in Modules/Setup (python#104293)
  pythonGH-104145: Use fully-qualified cross reference types for the bisect module (python#104172)
  pythongh-103193: Improve `getattr_static` test coverage (python#104286)
  Trim trailing whitespace and test on CI (python#104275)
  pythongh-102500: Remove mention of bytes shorthand (python#104281)
  pythongh-97696: Improve and fix documentation for asyncio eager tasks (python#104256)
  pythongh-99108: Replace SHA3 implementation HACL* version (python#103597)
  pythongh-104273: Remove redundant len() calls in argparse function (python#104274)
  pythongh-64660: Don't hardcode Argument Clinic return converter result variable name (python#104200)
  pythongh-104265 Disallow instantiation of `_csv.Reader` and `_csv.Writer` (python#104266)
  pythonGH-102613: Improve performance of `pathlib.Path.rglob()` (pythonGH-104244)
  pythongh-103650: Fix perf maps address format (python#103651)
  pythonGH-89812: Churn `pathlib.Path` methods (pythonGH-104243)
  ...
AlexWaygood added a commit to AlexWaygood/cpython that referenced this issue May 9, 2023
carljm added a commit to carljm/cpython that referenced this issue May 9, 2023
* main:
  pythongh-104276: Make `_struct.unpack_iterator` type use type flag instead of custom constructor (python#104277)
  pythongh-97696: Move around and update the whatsnew entry for asyncio eager task factory (python#104298)
  pythongh-103193: Fix refleaks in `test_inspect` and `test_typing` (python#104320)
  require-pr-label.yml: Add missing "permissions:" (python#104309)
  pythongh-90656: Add platform triplets for 64-bit LoongArch (LA64) (python#30939)
  pythongh-104180: Read SOCKS proxies from macOS System Configuration (python#104181)
carljm added a commit to carljm/cpython that referenced this issue May 9, 2023
* main: (29 commits)
  pythongh-104276: Make `_struct.unpack_iterator` type use type flag instead of custom constructor (python#104277)
  pythongh-97696: Move around and update the whatsnew entry for asyncio eager task factory (python#104298)
  pythongh-103193: Fix refleaks in `test_inspect` and `test_typing` (python#104320)
  require-pr-label.yml: Add missing "permissions:" (python#104309)
  pythongh-90656: Add platform triplets for 64-bit LoongArch (LA64) (python#30939)
  pythongh-104180: Read SOCKS proxies from macOS System Configuration (python#104181)
  pythongh-97696 Remove unnecessary check for eager_start kwarg (python#104188)
  pythonGH-104308: socket.getnameinfo should release the GIL (python#104307)
  pythongh-104310: Add importlib.util.allowing_all_extensions() (pythongh-104311)
  pythongh-99113: A Per-Interpreter GIL! (pythongh-104210)
  pythonGH-104284: Fix documentation gettext build (python#104296)
  pythongh-89550: Buffer GzipFile.write to reduce execution time by ~15% (python#101251)
  pythongh-104223: Fix issues with inheriting from buffer classes (python#104227)
  pythongh-99108: fix typo in Modules/Setup (python#104293)
  pythonGH-104145: Use fully-qualified cross reference types for the bisect module (python#104172)
  pythongh-103193: Improve `getattr_static` test coverage (python#104286)
  Trim trailing whitespace and test on CI (python#104275)
  pythongh-102500: Remove mention of bytes shorthand (python#104281)
  pythongh-97696: Improve and fix documentation for asyncio eager tasks (python#104256)
  pythongh-99108: Replace SHA3 implementation HACL* version (python#103597)
  ...
carljm added a commit to carljm/cpython that referenced this issue May 9, 2023
* main: (156 commits)
  pythongh-97696 Add documentation for get_coro() behavior with eager tasks (python#104304)
  pythongh-97933: (PEP 709) inline list/dict/set comprehensions (python#101441)
  pythongh-99889: Fix directory traversal security flaw in uu.decode() (python#104096)
  pythongh-104184: fix building --with-pydebug --enable-pystats (python#104217)
  pythongh-104139: Add itms-services to uses_netloc urllib.parse. (python#104312)
  pythongh-104240: return code unit metadata from codegen (python#104300)
  pythongh-104276: Make `_struct.unpack_iterator` type use type flag instead of custom constructor (python#104277)
  pythongh-97696: Move around and update the whatsnew entry for asyncio eager task factory (python#104298)
  pythongh-103193: Fix refleaks in `test_inspect` and `test_typing` (python#104320)
  require-pr-label.yml: Add missing "permissions:" (python#104309)
  pythongh-90656: Add platform triplets for 64-bit LoongArch (LA64) (python#30939)
  pythongh-104180: Read SOCKS proxies from macOS System Configuration (python#104181)
  pythongh-97696 Remove unnecessary check for eager_start kwarg (python#104188)
  pythonGH-104308: socket.getnameinfo should release the GIL (python#104307)
  pythongh-104310: Add importlib.util.allowing_all_extensions() (pythongh-104311)
  pythongh-99113: A Per-Interpreter GIL! (pythongh-104210)
  pythonGH-104284: Fix documentation gettext build (python#104296)
  pythongh-89550: Buffer GzipFile.write to reduce execution time by ~15% (python#101251)
  pythongh-104223: Fix issues with inheriting from buffer classes (python#104227)
  pythongh-99108: fix typo in Modules/Setup (python#104293)
  ...
carljm added a commit to carljm/cpython that referenced this issue May 9, 2023
* main: (35 commits)
  pythongh-97696 Add documentation for get_coro() behavior with eager tasks (python#104304)
  pythongh-97933: (PEP 709) inline list/dict/set comprehensions (python#101441)
  pythongh-99889: Fix directory traversal security flaw in uu.decode() (python#104096)
  pythongh-104184: fix building --with-pydebug --enable-pystats (python#104217)
  pythongh-104139: Add itms-services to uses_netloc urllib.parse. (python#104312)
  pythongh-104240: return code unit metadata from codegen (python#104300)
  pythongh-104276: Make `_struct.unpack_iterator` type use type flag instead of custom constructor (python#104277)
  pythongh-97696: Move around and update the whatsnew entry for asyncio eager task factory (python#104298)
  pythongh-103193: Fix refleaks in `test_inspect` and `test_typing` (python#104320)
  require-pr-label.yml: Add missing "permissions:" (python#104309)
  pythongh-90656: Add platform triplets for 64-bit LoongArch (LA64) (python#30939)
  pythongh-104180: Read SOCKS proxies from macOS System Configuration (python#104181)
  pythongh-97696 Remove unnecessary check for eager_start kwarg (python#104188)
  pythonGH-104308: socket.getnameinfo should release the GIL (python#104307)
  pythongh-104310: Add importlib.util.allowing_all_extensions() (pythongh-104311)
  pythongh-99113: A Per-Interpreter GIL! (pythongh-104210)
  pythonGH-104284: Fix documentation gettext build (python#104296)
  pythongh-89550: Buffer GzipFile.write to reduce execution time by ~15% (python#101251)
  pythongh-104223: Fix issues with inheriting from buffer classes (python#104227)
  pythongh-99108: fix typo in Modules/Setup (python#104293)
  ...
@AlexWaygood AlexWaygood self-assigned this May 31, 2023
@namka279

This comment was marked as spam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.12 bugs and security fixes performance Performance or resource usage stdlib Python modules in the Lib dir type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

3 participants