GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-85160: improve performance of singledispatchmethod by eendebakpt · Pull Request #107148 · python/cpython

Conversation

@eendebakpt

@eendebakpt eendebakpt commented Jul 23, 2023 •
edited by bedevere-bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

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

  • Issue: singledispatchmethod significantly slower than singledispatch #85160

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
mental32 and others added 8 commits November 10, 2020 07:05
@mental32
bpo-40988: Optimized singledispatchmethod access.
04fe64e
@AlexWaygood
Merge branch 'main' into bpo-40988
fc72bea
@eendebakpt
Merge branch 'main' into singledispatchmethod3
31d292b
@eendebakpt
add test for slotted classes
5ae8348
@eendebakpt
add test for slotted classes
1edc0e3
@eendebakpt
update new entry with attribution to original author
9a1ffae
@eendebakpt
add test for assignment to dispatched methods
3588943
@eendebakpt
typo
af233d4
@eendebakpt
eendebakpt requested a review from rhettinger as a code owner July 23, 2023 19:37
@bedevere-bot bedevere-bot mentioned this pull request Jul 23, 2023
singledispatchmethod significantly slower than singledispatch #85160
Closed
@bedevere-bot bedevere-bot added the awaiting review label Jul 23, 2023
@eendebakpt eendebakpt mentioned this pull request Jul 23, 2023
gh-85160: improve performance of singledispatchmethod #106448
Closed
@AlexWaygood
AlexWaygood requested review from AlexWaygood and corona10 and removed request for rhettinger July 23, 2023 19:42
AlexWaygood
AlexWaygood reviewed Jul 23, 2023
View reviewed changes
Comment thread Lib/functools.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

AlexWaygood
AlexWaygood reviewed Jul 23, 2023
View reviewed changes
Comment thread Lib/test/test_functools.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

eendebakpt and others added 3 commits July 23, 2023 21:56
@eendebakpt @AlexWaygood
Update Lib/test/test_functools.py
d5aebdf
Co-authored-by: Alex Waygood <[email protected]>
@eendebakpt
apply review suggestion
b773f82
@eendebakpt
Merge branch 'main' into singledispatchmethod3
25e1915
AlexWaygood
AlexWaygood reviewed Jul 23, 2023
View reviewed changes
Comment thread Lib/functools.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/functools.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

eendebakpt and others added 2 commits July 23, 2023 22:42
@eendebakpt @AlexWaygood
Update Lib/functools.py
593c923
Co-authored-by: Alex Waygood <[email protected]>
@eendebakpt @AlexWaygood
Update Lib/functools.py
39bec6d
Co-authored-by: Alex Waygood <[email protected]>
AlexWaygood
AlexWaygood reviewed Jul 23, 2023
View reviewed changes
Comment thread Lib/functools.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/functools.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt
use hasattr to check for slotted types
d954244
corona10
corona10 reviewed Jul 24, 2023
View reviewed changes
Comment thread Lib/functools.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt
make attrname private
df2eeb5
corona10
corona10 reviewed Jul 24, 2023
View reviewed changes
Comment thread Lib/functools.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

corona10
corona10 approved these changes Jul 24, 2023
View reviewed changes

@corona10 corona10 left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this 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 pyperf

runner = pyperf.Runner()
runner.timeit(name="bench singledispatchmethod",
              stmt="""
_ = t.go(1, 1)
""",
setup="""
from functools import singledispatch, singledispatchmethod

class Test:
    @singledispatchmethod
    def go(self, item, arg):
        pass

    @go.register
    def _(self, item: int, arg):
        return item + arg

t = Test()
""")

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

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

CaselIT reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@bedevere-bot bedevere-bot removed the awaiting review label Jul 24, 2023
eendebakpt and others added 3 commits July 31, 2023 13:59
@eendebakpt @AlexWaygood
Apply suggestions from code review
18d7e43
Co-authored-by: Alex Waygood <[email protected]>
@eendebakpt
Merge branch 'main' into singledispatchmethod3
755efdd
@AlexWaygood
Ensure we only weakref() obj once
56d342c
@AlexWaygood

AlexWaygood commented Jul 31, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

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 at eendebakpt#5, which also adds some more test coverage.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

AlexWaygood and others added 5 commits July 31, 2023 15:06
@AlexWaygood
Get rid of the cache local variable
a119e21
@AlexWaygood
Don't assign caching in the fast path
47c18a5
@eendebakpt
Merge pull request #5 from AlexWaygood/even-more-singledispatchmethod…
2031680
…-perf

Ensure we only weakref() `obj` once
@eendebakpt
remove check on obj being None
ca691e8
@eendebakpt
eliminate caching variable
e8eeedd
@eendebakpt

eendebakpt commented Jul 31, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

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

One more optimization is in https://github.com/eendebakpt/cpython/pull/6/files. It eliminates the caching variable. The assumption there is that if self._method_cache[obj] returns a KeyError, the expression self._method_cache[obj] = ... is safe.

I still have to do benchmarking on this.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

eendebakpt and others added 2 commits August 1, 2023 10:10
@eendebakpt
Merge pull request #6 from eendebakpt/singledispatchmethod3b
307f0c1
Eliminate caching variable
@AlexWaygood
Test slotted staticmethods on instances as well as the class
afea7b9
AlexWaygood
AlexWaygood approved these changes Aug 1, 2023
View reviewed changes

@AlexWaygood AlexWaygood left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this 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?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@eendebakpt

eendebakpt commented Aug 1, 2023

Copy link
Copy Markdown
Contributor Author

Latest benchmarks:

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

Geometric 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 faster
bench classmethod on instance: Mean +- std dev: [pr] 1.12 us +- 0.01 us -> [pr_lookup] 1.06 us +- 0.01 us: 1.06x faster
bench slotted class: Mean +- std dev: [pr] 2.53 us +- 0.03 us -> [pr_lookup] 2.52 us +- 0.03 us: 1.01x faster

Benchmark hidden because not significant (1): bench classmethod

Geometric mean: 1.03x faster
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AlexWaygood

AlexWaygood commented Aug 1, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

One more variation is to eliminate attribute lookups in the dispatch wrapper: eendebakpt/[email protected]:cpython:singledispatchmethod3b

I can reproduce the speedup locally (great spot!), and it seems like a reasonable thing to do. Please name the variable dispatch rather than dp, 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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt
eliminate method variable and reduce attribute lookups
b43dcee
@AlexWaygood

AlexWaygood commented Aug 1, 2023

Copy link
Copy Markdown
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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AlexWaygood
AlexWaygood merged commit 3e334ae into python:main Aug 6, 2023
@bedevere-bot bedevere-bot removed the awaiting merge label Aug 6, 2023
@AlexWaygood

AlexWaygood commented Aug 6, 2023

Copy link
Copy Markdown
Member

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

mental32 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AlexWaygood AlexWaygood mentioned this pull request Aug 6, 2023
gh-85160: Optimized singledispatchmethod access (noticeable improvement). #23213
Closed
AlexWaygood
AlexWaygood reviewed Aug 6, 2023
View reviewed changes
Comment thread Lib/functools.py
try:
_method = self._method_cache[obj]
except TypeError:
self._all_weakrefable_instances = False

@AlexWaygood AlexWaygood Aug 6, 2023

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

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 the WeakKeyDictionary at all)

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

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

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@eendebakpt eendebakpt Aug 6, 2023

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

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 simple del looks cleaner.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@AlexWaygood AlexWaygood Aug 6, 2023

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

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 with del

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@AlexWaygood AlexWaygood mentioned this pull request Aug 7, 2023
gh-85160: Reduce memory usage of singledispatchmethod when owner instances cannot be weakref'd #107706
Merged
AlexWaygood added a commit that referenced this pull request Aug 7, 2023
@AlexWaygood @serhiy-storchaka
gh-85160: Reduce memory usage of singledispatchmethod (#107706)
2ac103c
A small followup to #107148

Co-authored-by: Serhiy Storchaka <[email protected]>
@blaggacao blaggacao mentioned this pull request Dec 4, 2024
chore: python3.13 support frappe/frappe#28624
Merged
@eendebakpt eendebakpt mentioned this pull request Dec 9, 2024
gh-127750: Improve caching in singledispatchmethod #127751
Closed
@eendebakpt eendebakpt mentioned this pull request Jan 8, 2025
gh-127750: Fix singledispatchmethod caching (v2) #128648
Closed
eendebakpt added a commit to eendebakpt/cpython that referenced this pull request Feb 12, 2025
@eendebakpt
Revert pythongh-107148
7606147
@eendebakpt eendebakpt mentioned this pull request Feb 12, 2025
[3.13] gh-127750: Fix functools.singledispatchmethod() #130029
Merged
serhiy-storchaka pushed a commit that referenced this pull request Feb 17, 2025
@eendebakpt
[3.13] gh-127750: Fix functools.singledispatchmethod() (GH-130029)
8f33c7a
Revert gh-107148
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@AlexWaygood AlexWaygood AlexWaygood approved these changes
@corona10 corona10 corona10 approved these changes
@ambv ambv Awaiting requested review from ambv

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

6 participants

@eendebakpt @emmatyping @AlexWaygood @corona10 @bedevere-bot @mental32
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.