-2

Assuming we have a function that updates a bunch of internal values within a class like so:

class MyClass:
    def __init__():
        self.counter = 0
        self.condition_1 = True
        self.condition_2 = False
    def update():
        if self.condition_1:
            if self.condition_2:
                self.counter += 2
                # return or not?
            else:
                self.counter += 1
                # return or not?
        else:
            self.counter -= 1
            # return or not?

Would the update function be executed faster with or without a return statement within it (after updating variables)? Or would it be 100% the same? (unlikely for me)

I know this sounds like a trivial/dumb question to ask without context, but consider that this function is being called repeatedly thousands of times so slight increase in performance within the function can have a large impact on how fast the whole program takes to execute.

In my real program, the conditions within the update function are very complex and more nested; the program processes a lot of data as well.

Random
  • 13
  • 3
  • 3
    Why not just benchmark it and see? What information could you get here that wouldn’t be answered by said benchmark? – esqew Mar 11 '23 at 00:11
  • 2
    I suggest you [`timeit`](https://docs.python.org/3/library/timeit.html) and see. Also, you might try sorting the data and measuring it again. – Elliott Frisch Mar 11 '23 at 00:11
  • Python has enough of a peephole optimizer that I sincerely doubt there is any measurable difference. – Tim Roberts Mar 11 '23 at 00:13

1 Answers1

2

You can look at the produced bytecode to answer this:

With explicit returns:

  4           0 LOAD_CONST               1 (0)
              2 STORE_FAST               0 (counter)
  5           4 LOAD_FAST                0 (counter)
              6 LOAD_CONST               1 (0)
              8 COMPARE_OP               2 (==)
             10 POP_JUMP_IF_FALSE       22 (to 44)
  6          12 LOAD_FAST                0 (counter)
             14 LOAD_CONST               2 (1)
             16 COMPARE_OP               2 (==)
             18 POP_JUMP_IF_FALSE       16 (to 32)
  7          20 LOAD_FAST                0 (counter)
             22 LOAD_CONST               3 (2)
             24 INPLACE_ADD
             26 STORE_FAST               0 (counter)
  8          28 LOAD_CONST               0 (None)
             30 RETURN_VALUE
 10     >>   32 LOAD_FAST                0 (counter)
             34 LOAD_CONST               2 (1)
             36 INPLACE_ADD
             38 STORE_FAST               0 (counter)
 11          40 LOAD_CONST               0 (None)
             42 RETURN_VALUE
 13     >>   44 LOAD_FAST                0 (counter)
             46 LOAD_CONST               2 (1)
             48 INPLACE_SUBTRACT
             50 STORE_FAST               0 (counter)
 14          52 LOAD_CONST               0 (None)
             54 RETURN_VALUE

Without explicit returns:

  4           0 LOAD_CONST               1 (0)
              2 STORE_FAST               0 (counter)
  5           4 LOAD_FAST                0 (counter)
              6 LOAD_CONST               1 (0)
              8 COMPARE_OP               2 (==)
             10 POP_JUMP_IF_FALSE       22 (to 44)
  6          12 LOAD_FAST                0 (counter)
             14 LOAD_CONST               2 (1)
             16 COMPARE_OP               2 (==)
             18 POP_JUMP_IF_FALSE       16 (to 32)
  7          20 LOAD_FAST                0 (counter)
             22 LOAD_CONST               3 (2)
             24 INPLACE_ADD
             26 STORE_FAST               0 (counter)
             28 LOAD_CONST               0 (None)
             30 RETURN_VALUE
 10     >>   32 LOAD_FAST                0 (counter)
             34 LOAD_CONST               2 (1)
             36 INPLACE_ADD
             38 STORE_FAST               0 (counter)
             40 LOAD_CONST               0 (None)
             42 RETURN_VALUE
 13     >>   44 LOAD_FAST                0 (counter)
             46 LOAD_CONST               2 (1)
             48 INPLACE_SUBTRACT
             50 STORE_FAST               0 (counter)
             52 LOAD_CONST               0 (None)
             54 RETURN_VALUE

Although it's hard to see manually, they result in identical bytecode in this particular case, with Python 3.10 (verified with diffchecker; although any text comparison tool would work). The only difference is the loading of None is associated with a line with the explicit return version.

Both result in the same compiled bytes:

>>> dis.Bytecode(update).codeobj.co_code
 b'd\x01}\x00|\x00d\x01k\x02r\x16|\x00d\x02k\x02r\x10|\x00d\x027\x00}\x00d\x00S\x00|\x00d\x037\x00}\x00d\x00S\x00|\x00d\x038\x00}\x00d\x00S\x00'

Test code:

import dis

def update():
    counter = 0
    if counter == 0:
        if counter == 1:
            counter += 2
            return
        else:
            counter += 1
            return
    else:
        counter -= 1
        return

dis.dis(update)
Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
  • @KellyBundy You're right. I should have realized that something was wrong due to the lack of jumps. That's my bad. Updated. Thankfully, the conclusion is the same; at least with this test-case. – Carcigenicate Mar 11 '23 at 00:59
  • Yes, I also got identical bytecodes except for the line numbers (not sure whether that affects anything... I guess it *could* make the code object slightly larger and thereby affect caching or so). Might still depend on the Python version, I only tried one. I btw used [diffchecker](https://www.diffchecker.com/QBnkXLaa/), which lets you share diffs. – Kelly Bundy Mar 11 '23 at 01:07
  • @KellyBundy Ya, that's what I used too. Works well as long as you ensure the line numbers are the same by overwriting the code instead of having two different versions of the functions like I had originally. And if I check the actual bytes in each case using `dis.Bytecode(update).codeobj.co_code`, they're the same in both places. I believe the line number difference is just to make reading the disassembly easier. – Carcigenicate Mar 11 '23 at 01:15