3

I like to work with classes and methods instead of bare functions. I am wondering if there is a specific performance impact to do so (either in execution speed, or memory usage or in other aspects).

A quick test shows that both perform equally well:

import timeit

class Hello:
    def hello(self):
        x = 9 * 8 + 3**5

def world():
    x = 9 * 8 + 3 ** 5

print(timeit.timeit(world, number=10000000))
h = Hello()
print(timeit.timeit(h.hello, number=10000000))
# 0.8460009839758439
# 0.8781686117747095

In other tests I did not see the RAM being used more in one case than in the other.

Are there specific cases where performance will be degraded when using a class/method instead of a function?

Note: I would like to focus exclusively on code performance, not aesthetical aspects

1
  • Try wrapping those in a lambda and testing again. Commented Mar 21, 2017 at 11:01

2 Answers 2

4

The overhead of method invocation is really just the transformation of the function object to a method object when attribute access (.) on a function attribute it made from an instance.

Apart from that, the calling of the function is similar, with one extra argument (self) inserted implicitly for the method.

So, no, there aren't really any concerns to have here, the overhead is small and can be completely eliminated by assigning the method to a local variable:

meth = h.hello 
# use meth from now on

(Edit: In Python 3.7 new op-codes where introduced that basically negate the benefit of assigning h.hello to a local name, the look-up for methods just got quite faster :-)

If you're looking for bottlenecks, you should be looking elsewhere. Python's dynamic interpretation really makes these sort of concerns pedantic.


As for the memory aspect, methods should be bit larger than the functions due to a method essentially containing a function as one of its members:

meth.__func__ # original function object

Despite this, I can't imagine a scenario where your application would choke due to the slight memory overhead methods introduce.

In CPython, for example, approximately 64 bytes are are added for a bound method object according to getsizeof:

>>> getsizeof(Foo().foo)
64

This isn't counting the __func__ attribute, which contains the function objects:

>>> getsizeof(Foo().foo.__func__)
136
Sign up to request clarification or add additional context in comments.

Comments

1

Calling a method obj.method(...) involves attribute access (the obj.method part) which can be a non-trivial and therefore quite costly operation. A brief description of a possible attribute access scenario is found in the documentation of the descriptor protocol:

The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses.

However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined and how they were called.

Only after the attribute access is complete, invoking the resultant callable object is little different from calling a free function. Note, however, that in your benchmark the overhead of attribute access, i.e. the operations behind the seemingly innocuous expression h.hello, is not measured (though in your example it should be quite small).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.