This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: singledispatchmethod significantly slower than singledispatch
Type: performance Stage: patch review
Components: Library (Lib) Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: CaselIT, mental
Priority: normal Keywords: patch

Created on 2020-06-15 20:14 by CaselIT, last changed 2022-04-11 14:59 by admin.

Pull Requests
URL Status Linked Edit
PR 23213 open mental, 2020-11-10 05:12
Messages (1)
msg371594 - (view) Author: Federico Caselli (CaselIT) Date: 2020-06-15 20:14
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
History
Date User Action Args
2022-04-11 14:59:32adminsetgithub: 85160
2020-11-10 05:12:05mentalsetkeywords: + patch
nosy: + mental

pull_requests: + pull_request22112
stage: patch review
2020-06-15 20:14:13CaselITcreate