Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-85160: improve performance of singledispatchmethod#107148

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
AlexWaygood merged 32 commits intopython:mainfromeendebakpt:singledispatchmethod3
Aug 6, 2023

Conversation

@eendebakpt
Copy link
Contributor

@eendebakpteendebakpt commentedJul 23, 2023
edited by bedevere-bot
Loading

This PR implements the idea from#85160 to improve performance of the singledispatchmethod by caching the generated dispatch method. It is a continuation of#23213

Also see#106448

eendebakptand others added2 commitsJuly 23, 2023 22:42
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Copy link
Member

@corona10corona10 left a comment

Choose a reason for hiding this comment

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

lgtm with nit comment

It's become a lot faster

script

import pyperfrunner = pyperf.Runner()runner.timeit(name="bench singledispatchmethod",              stmt="""_ = t.go(1, 1)""",setup="""from functools import singledispatch, singledispatchmethodclass Test:    @singledispatchmethod    def go(self, item, arg):        pass    @go.register    def _(self, item: int, arg):        return item + argt = Test()""")

Mean +- std dev: [base] 1.37 us +- 0.01 us -> [opt] 410 ns +- 2 ns: 3.35x faster

CaselIT reacted with thumbs up emoji
@AlexWaygood
Copy link
Member

AlexWaygood commentedJul 31, 2023
edited
Loading

I did some more playing around locally and found we could improve performance even more with a few tweaks. Proposed a PR to your branch ateendebakpt#5, which also adds some more test coverage.

@eendebakpt
Copy link
ContributorAuthor

eendebakpt commentedJul 31, 2023
edited
Loading

@AlexWaygood I merged your PR and added one more optimization: we can skip theobj is not None check since forobj=None the weakref indexing will fail with aTypeError.

One more optimization is inhttps://github.com/eendebakpt/cpython/pull/6/files. It eliminates the caching variable. The assumption there is that ifself._method_cache[obj] returns aKeyError, the expressionself._method_cache[obj] = ... is safe.

I still have to do benchmarking on this.

Copy link
Member

@AlexWaygoodAlexWaygood left a comment

Choose a reason for hiding this comment

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

LGTM.@ethanhs, how does this look to you now?

@eendebakpt
Copy link
ContributorAuthor

Latest benchmarks:

bench singledispatchmethod: Mean +- std dev: [main] 2.48 us +- 0.02 us -> [pr] 1.04 us +- 0.02 us: 2.38x fasterbench classmethod: Mean +- std dev: [main] 3.45 us +- 0.06 us -> [pr] 3.50 us +- 0.07 us: 1.01x slowerbench classmethod on instance: Mean +- std dev: [main] 3.49 us +- 0.06 us -> [pr] 1.12 us +- 0.01 us: 3.11x fasterbench slotted class: Mean +- std dev: [main] 2.48 us +- 0.03 us -> [pr] 2.53 us +- 0.03 us: 1.02x slowerGeometric mean: 1.64x faster

So the performance improves for the common case (normal methods) and is more or less equal for class methods and slotted classes.

One more variation is to eliminate attribute lookups in the dispatch wrapper:eendebakpt/cpython@singledispatchmethod3...eendebakpt:cpython:singledispatchmethod3b
This results in (compared to this PR):

bench singledispatchmethod: Mean +- std dev: [pr] 1.04 us +- 0.02 us -> [pr_lookup] 979 ns +- 15 ns: 1.06x fasterbench classmethod on instance: Mean +- std dev: [pr] 1.12 us +- 0.01 us -> [pr_lookup] 1.06 us +- 0.01 us: 1.06x fasterbench slotted class: Mean +- std dev: [pr] 2.53 us +- 0.03 us -> [pr_lookup] 2.52 us +- 0.03 us: 1.01x fasterBenchmark hidden because not significant (1): bench classmethodGeometric mean: 1.03x faster

@AlexWaygood
Copy link
Member

AlexWaygood commentedAug 1, 2023
edited
Loading

One more variation is to eliminate attribute lookups in the dispatch wrapper:eendebakpt/cpython@singledispatchmethod3...eendebakpt:cpython:singledispatchmethod3b

I can reproduce the speedup locally (great spot!), and it seems like a reasonable thing to do. Please name the variabledispatch rather thandp, though :)

One other optimisation that shaves around 5 microseconds off the benchmark for slotted classes and other objects that can't be weakref'd (but doesn't do much for those that can):

--- a/Lib/functools.py+++ b/Lib/functools.py@@ -959,7 +959,7 @@ def _method(*args, **kwargs):             method = self.dispatcher.dispatch(args[0].__class__)             return method.__get__(obj, cls)(*args, **kwargs)-        _method.__isabstractmethod__ = self.__isabstractmethod__+        _method.__isabstractmethod__ = getattr(self.func, "__isabstractmethod__", False)

But if you want to do ^that, you should probably add a comment about why we're duplicating the logic in the__isabstractmethod__ property.

@AlexWaygood
Copy link
Member

One other optimisation that shaves around 5 microseconds off the benchmark for slotted classes and other objects that can't be weakref'd (but doesn't do much for those that can):

--- a/Lib/functools.py+++ b/Lib/functools.py@@ -959,7 +959,7 @@ def _method(*args, **kwargs):             method = self.dispatcher.dispatch(args[0].__class__)             return method.__get__(obj, cls)(*args, **kwargs)-        _method.__isabstractmethod__ = self.__isabstractmethod__+        _method.__isabstractmethod__ = getattr(self.func, "__isabstractmethod__", False)

Hmm, I thought I could see a speedup from this earlier, but now I can no longer reproduce it. I assume it was only noise -- best to leave it.

@AlexWaygoodAlexWaygood merged commit3e334ae intopython:mainAug 6, 2023
@AlexWaygood
Copy link
Member

@mental32 and@eendebakpt, thanks so much for all your work on this!

mental32 reacted with thumbs up emoji

try:
_method=self._method_cache[obj]
exceptTypeError:
self._all_weakrefable_instances=False
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I wonder if this would have been a good idea, to keep memory usage down for the slow path (since in the slow path, we don't really need theWeakKeyDictionary at all)

Suggested change
self._all_weakrefable_instances=False
self._all_weakrefable_instances=False
delself._method_cache

@corona10, what do you think? Does it matter?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

One could even delay the creation of the Weakkeydictionary untill the first invocation of__get__. The code will look a bit odd though, a simpledel looks cleaner.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think I prefer the approach withdel

AlexWaygood added a commit that referenced this pull requestAug 7, 2023
A small followup to#107148Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
eendebakpt added a commit to eendebakpt/cpython that referenced this pull requestFeb 12, 2025
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@AlexWaygoodAlexWaygoodAlexWaygood approved these changes

@corona10corona10corona10 approved these changes

@ambvambvAwaiting requested review from ambv

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

6 participants

@eendebakpt@emmatyping@AlexWaygood@corona10@bedevere-bot@mental32

[8]ページ先頭

©2009-2025 Movatter.jp