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

gh-118013: Use weakrefs for the cache key in inspect._shadowed_dict #118202

Merged
merged 9 commits into from
Apr 24, 2024

Conversation

AlexWaygood
Copy link
Member

@AlexWaygood AlexWaygood commented Apr 23, 2024

Slower than main, but significantly faster than a straight revert of #104267.

@AlexWaygood AlexWaygood requested a review from carljm April 23, 2024 23:12
Copy link
Member

@carljm carljm left a comment

Choose a reason for hiding this comment

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

Looks good to me, once we get news/tests/benchmarks.

Lib/inspect.py Show resolved Hide resolved
@AlexWaygood AlexWaygood marked this pull request as ready for review April 24, 2024 11:23
@AlexWaygood
Copy link
Member Author

Benchmarks

All timings done with a fresh PGO-optimised build on main using a MacBook pro.

getattr_static benchmarks:

Benchmark script
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())
Results on main
type[Foo]                :   26 ±  1 ns 
Foo                      :   44 ±  0 ns 
type[Bar]                :   26 ±  0 ns 
Bar                      :   40 ±  0 ns 
WithParentClassX         :   52 ±  0 ns 
Baz                      :   49 ±  0 ns 
WithParentX              :   59 ±  0 ns 
type[Missing]            :   56 ±  0 ns 
Missing                  :   50 ±  0 ns 
Slotted                  :   57 ±  0 ns 
Method                   :   39 ±  0 ns 
StMethod                 :   39 ±  0 ns 
ClsMethod                :   39 ±  0 ns
Results with this PR
type[Foo]                :   39 ±  0 ns 
Foo                      :   60 ±  1 ns 
type[Bar]                :   33 ±  0 ns 
Bar                      :   56 ±  0 ns 
WithParentClassX         :   79 ±  0 ns 
Baz                      :   72 ±  0 ns 
WithParentX              :   92 ±  0 ns 
type[Missing]            :   86 ±  0 ns 
Missing                  :   74 ±  0 ns 
Slotted                  :   74 ±  0 ns 
Method                   :   56 ±  0 ns 
StMethod                 :   56 ±  0 ns 
ClsMethod                :   56 ±  0 ns
Results with a clean revert of #104267
type[Foo]                :   43 ±  0 ns 
Foo                      :   66 ±  1 ns 
type[Bar]                :   37 ±  0 ns 
Bar                      :   65 ±  0 ns 
WithParentClassX         :   91 ±  0 ns 
Baz                      :   84 ±  0 ns 
WithParentX              :  111 ±  0 ns 
type[Missing]            :  103 ±  0 ns 
Missing                  :   85 ±  0 ns 
Slotted                  :   78 ±  0 ns 
Method                   :   65 ±  0 ns 
StMethod                 :   65 ±  0 ns 
ClsMethod                :   65 ±  0 ns

Runtime-checkable protocol benchmarks

Benchmark script
from typing import Protocol, runtime_checkable

@runtime_checkable
class Foo(Protocol):
    a: int
    b: int
    c: int
    d: int
    e: int
    f: int
    g: int
    h: int
    i: int
    j: int
    k: int
    l: int
    m: int
    n: int
    o: int
    p: int
    q: int
    r: int
    s: int
    t: int
    u: int
    v: int
    w: int
    x: int
    y: int
    z: int

class Bar:
    def __init__(self):
        for attrname in 'abcdefghijklmnopqrstuvwxyz':
            setattr(self, attrname, 42)

bars = [Bar() for _ in range(100_000)]

import time
start = time.perf_counter()
for bar in bars:
    isinstance(bar, Foo)
print(time.perf_counter() - start)

Results on main: 1.3446190829854459

Results with this PR: 1.9680303339846432

Results with a clean revert of #104267: 2.243281374918297

@AlexWaygood AlexWaygood requested a review from carljm April 24, 2024 11:39
@AlexWaygood AlexWaygood added the needs backport to 3.12 bug and security fixes label Apr 24, 2024
@AlexWaygood
Copy link
Member Author

(The getattr_static() benchmark script was originally contributed by @sobolevn in #103193 (comment))

Copy link
Member

@carljm carljm left a comment

Choose a reason for hiding this comment

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

🎉

Lib/test/test_inspect/test_inspect.py Show resolved Hide resolved
@AlexWaygood AlexWaygood merged commit 8227883 into python:main Apr 24, 2024
43 checks passed
@AlexWaygood AlexWaygood deleted the inspect-weakref-cache-key branch April 24, 2024 14:55
@miss-islington-app
Copy link

Thanks @AlexWaygood for the PR 🌮🎉.. I'm working now to backport this PR to: 3.12.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Apr 24, 2024
…_dict` (pythonGH-118202)

(cherry picked from commit 8227883)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
@bedevere-app
Copy link

bedevere-app bot commented Apr 24, 2024

GH-118232 is a backport of this pull request to the 3.12 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.12 bug and security fixes label Apr 24, 2024
AlexWaygood added a commit that referenced this pull request Apr 24, 2024
…d_dict` (GH-118202) (#118232)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants