1

Dear experienced friends, I have a question about the speed of calling a function when I use the Python class. Suppose we have two scenarios:

  1. We define the function inside the class, and run this function when we use the class.
class play_in():

    def __init__(self, n):
        super().__init__()
        self.running_time = n
        
    def __call__(self):
        output = play_in.calculate_running_time_in(self.running_time)
        return print(output)
        
    @staticmethod
    def calculate_running_time_in(n):
        t1 = time.time()
        for _ in range(n):
            c = 9999*9999/32
        return time.time()-t1
  1. We define the function outside the class, and call this function inside the class.
# define fun outside
def calculate_running_time_out(n):
    t1 = time.time()
    for _ in range(n):
        c = 9999*9999/32
    return time.time()-t1

class play_out():

    def __init__(self, n):
        super().__init__()
        self.running_time = n
        
    def __call__(self):
        output = calculate_running_time_out(self.running_time)
        return print(output)

Then I run these two function-calling methods several times. However, the difference between them is not stable. Sometimes the in-calling is faster, sometimes the out-calling is faster.

test_time = 10000000

test_model_in = play_in(test_time)
test_model_in()

test_model_out = play_out(test_time)
test_model_out()

So I am wondering: Is there a difference (or theoretical difference) between these two calling methods? If so, which one is better?

Thank you so much in advance!

Nick Nick Nick
  • 159
  • 3
  • 13
  • 2
    This is not how you [benchmark Python code](https://stackoverflow.com/questions/1593019/is-there-any-simple-way-to-benchmark-python-script) – OneCricketeer Aug 06 '21 at 04:52
  • Thank you for the comment @OneCricketeer! Yes, what I described here is definitely not a rigorous way. But while I work on the Cloud, I am afraid the status of the server may affect the time. So I am seeking some theoretical explanation here. Thank you anyway! – Nick Nick Nick Aug 06 '21 at 04:56
  • Your test is just timing how long two identical loops take to complete? – Iain Shelvington Aug 06 '21 at 04:56
  • Yes, because I want to control the variable, which means the only difference is: how we call the executive function. One is inside the class, another is outside. So we can infer the difference. Please let me know if anything I made is wrong. – Nick Nick Nick Aug 06 '21 at 04:59
  • 1
    My point is that you should be using `timeit`, not `time.time()`, which itself effects the actual runtime of the functions – OneCricketeer Aug 06 '21 at 05:00
  • Just ran some tests and using a staticmethod is slower, I suspect because the staticmethod has to be looked up on the class every time and the function is retrieved from globals instead – Iain Shelvington Aug 06 '21 at 05:07
  • I assume in the first example that should be `play_in`, not `play`. Anyway, you'll want to change that to `output = self.calculate_running_time_in(self.running_time)` which is the normal way you'd handle the static method so that an inheriting class also has the opportunity to override the static method. It may also be faster. – tdelaney Aug 06 '21 at 05:08
  • Polymorphism is the primary reason to use a static method, along with reducing module level namespace pollution. – tdelaney Aug 06 '21 at 05:11

2 Answers2

3

Adding to Iain Shelvington's answer, decompiling shows the static method option has more op codes

  6           0 LOAD_FAST                0 (self)
              2 LOAD_METHOD              0 (calc)
              4 CALL_METHOD              0
              6 RETURN_VALUE

than the global

 15           0 LOAD_GLOBAL              0 (calc)
              2 CALL_FUNCTION            0
              4 RETURN_VALUE

The LOAD_METHOD op code needs to traverse the class tree to find the right version of the static method. I suppose that means the static method slows as class inheritance increases. But polymorphism is a primary reason for static methods in the first place, so you gotta pay to play.

Less obvious is that you run through some staticmethod C code (see https://github.com/python/cpython/blob/main/Objects/funcobject.c). Decorators add a bit of overhead to calls.

tdelaney
  • 73,364
  • 6
  • 83
  • 116
2

Here is a way to test the difference between using a staticmethod and a separate function, the function and method do nothing so this should be a good enough way to isolate just the behaviour we want to test. On my Python I consistently get better results when not using staticmethod

import timeit


class play_in:

    def __call__(self):
        return self.calc()

    @staticmethod
    def calc():
        pass


class play_out:

    def __call__(self):
        return calc()


def calc():
    pass


play_in_test = play_in()
print(timeit.timeit('play_in_test()', number=1000000, globals=globals()))

play_out_test = play_out()
print(timeit.timeit('play_out_test()', number=1000000, globals=globals()))
Iain Shelvington
  • 31,030
  • 3
  • 31
  • 50
  • 2
    Typical result for me was 0.41991179500473663 verses 0.4270418929954758, or a difference of approx 0.004 over 1 million tries, or .004 uS per try. So, technically faster but I think it should also be noted that the difference is vanishingly small. – tdelaney Aug 06 '21 at 05:24
  • 2
    @tdelaney yeah the difference is fairly minimal, on my machine I got a bigger difference 0.157 verses 0.144 – Iain Shelvington Aug 06 '21 at 05:26
  • Thank you so much for pointing it out! It is quite an unexpected result, because I was thinking calling the function outside class might be more time-consuming. Also thank you both for pointing out a rigorous time-testing method. I really appreciate it! – Nick Nick Nick Aug 06 '21 at 05:30