Skip to content

singledispatchmethod significantly slower than singledispatch #85160

Closed

Description

@CaselIT
mannequin
BPO 40988
Nosy @mental32, @CaselIT
PRs
  • gh-85160: Optimized singledispatchmethod access (noticeable improvement). #23213
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2020-06-15.20:14:13.251>
    labels = ['3.8', 'library', 'performance']
    title = 'singledispatchmethod significantly slower than singledispatch'
    updated_at = <Date 2020-11-10.05:12:05.587>
    user = 'https://github.com/CaselIT'

    bugs.python.org fields:

    activity = <Date 2020-11-10.05:12:05.587>
    actor = 'mental'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2020-06-15.20:14:13.251>
    creator = 'CaselIT'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 40988
    keywords = ['patch']
    message_count = 1.0
    messages = ['371594']
    nosy_count = 2.0
    nosy_names = ['mental', 'CaselIT']
    pr_nums = ['23213']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'performance'
    url = 'https://bugs.python.org/issue40988'
    versions = ['Python 3.8']

    Linked PRs

    Activity

    1. CaselIT commented on Jun 15, 2020

      CaselITmannequin
      MannequinAuthor

      The implementation of singledispatchmethod is significantly slower (~4x) than the normal singledispatch version

      Using timeit to test this example case:

          from functools import singledispatch, singledispatchmethod
          import timeit
      
          class Test:
              @singledispatchmethod
              def go(self, item, arg):
                  print('general')
              
              @go.register
              def _(self, item:int, arg):
                  return item + arg
      
          @singledispatch
          def go(item, arg):
              print('general')
      
          @go.register
          def _(item:int, arg):
              return item + arg
      print(timeit.timeit('t.go(1, 1)', globals={'t': Test()}))
      print(timeit.timeit('go(1, 1)', globals={'go': go}))
      

      Prints on my system.

      3.118346
      0.713173
      

      Looking at the singledispatchmethod implementation I believe that most of the difference is because a new function is generated every time the method is called.

      Maybe an implementation similar to cached_property could be used if the class has __dict__ attribute?
      Trying this simple patch

      diff --git a/Lib/functools.py b/Lib/functools.py
      index 5cab497..e42f485 100644
      --- a/Lib/functools.py
      +++ b/Lib/functools.py
      @@ -900,6 +900,7 @@ class singledispatchmethod:
      
                  self.dispatcher = singledispatch(func)
                  self.func = func
          +        self.attrname = None
      
              def register(self, cls, method=None):
                  """generic_method.register(cls, func) -> func
          @@ -908,6 +909,10 @@ class singledispatchmethod:
                  """
                  return self.dispatcher.register(cls, func=method)
      +    def __set_name__(self, owner, name):
      +        if self.attrname is None:
      +            self.attrname = name
      +
          def __get__(self, obj, cls=None):
              def _method(*args, **kwargs):
                  method = self.dispatcher.dispatch(args[0].__class__)
      @@ -916,6 +921,7 @@ class singledispatchmethod:
              _method.__isabstractmethod__ = self.__isabstractmethod__
              _method.register = self.register
              update_wrapper(_method, self.func)
      +        obj.__dict__[self.attrname] = _method
              return _method
      
          @property
      

      improves the performance noticeably

      0.9720976
      0.7269078
      
    2. added
      stdlibStandard Library Python modules in the Lib/ directory
      performancePerformance or resource usage
      on Jun 15, 2020
    3. transferred this issue fromon Apr 10, 2022
    4. added
      3.12only security fixes
      and removed on Sep 9, 2022
    5. iritkatriel commented on Sep 9, 2022

      @iritkatriel
      Member

      I see a similar time difference on main.

    6. added
      type-featureA feature request or enhancement
      and removed
      3.12only security fixes
      on Jul 5, 2023
    7. added a commit that references this issue on Aug 6, 2023
    8. AlexWaygood commented on Aug 6, 2023

      @AlexWaygood
      Member

      #107148 significantly optimised @singledispatchmethod for all non-slotted classes. If anybody can think of further optimisations that can be safely applied, I suggest a new issue should be opened for them.

      Thanks @casselt for the report, and thanks @mental32 and @eendebakpt for working on this!

    9. added 2 commits that reference this issue on Aug 7, 2023
    10. added 2 commits that reference this issue on Feb 11, 2025
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      performancePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions