1

Is there any difference between these two sample functions, performance-wise? If yes, why?

def func(a):
    if a > 0:
        num = 1
    else:
        num = -1
    return num

and

def func(a):
    if a > 0:
        return 1
    else:
        return -1
3
  • 1
    I would prefer return 1 if a > 0 else -1 Commented Apr 6, 2014 at 8:19
  • 4
    Is this really the bottleneck? Commented Apr 6, 2014 at 8:20
  • 1
    For all practical purposes, the answer is No. Commented Apr 6, 2014 at 8:44

4 Answers 4

4

If you're really interested, you can get an idea of how fast a function will run using dis; more lines is probably worse:

return at end:

  4           0 LOAD_FAST                0 (a) 
              3 LOAD_CONST               1 (0) 
              6 COMPARE_OP               4 (>) 
              9 POP_JUMP_IF_FALSE       21 

  5          12 LOAD_CONST               2 (1) 
             15 STORE_FAST               1 (num) 
             18 JUMP_FORWARD             6 (to 27) 

  7     >>   21 LOAD_CONST               3 (-1) 
             24 STORE_FAST               1 (num) 

  8     >>   27 LOAD_FAST                1 (num) 
             30 RETURN_VALUE

11 lines, pretty fast.

return in if:

 13           0 LOAD_FAST                0 (a) 
              3 LOAD_CONST               1 (0) 
              6 COMPARE_OP               4 (>) 
              9 POP_JUMP_IF_FALSE       16 

 14          12 LOAD_CONST               2 (1) 
             15 RETURN_VALUE         

 16     >>   16 LOAD_CONST               3 (-1) 
             19 RETURN_VALUE         
             20 LOAD_CONST               0 (None) 
             23 RETURN_VALUE         

10 lines, probably slightly faster.

And thefourtheye's suggestion:

def func(a):
    return 1 if a > 0 else -1

Gives:

 21           0 LOAD_FAST                0 (a) 
              3 LOAD_CONST               1 (0) 
              6 COMPARE_OP               4 (>) 
              9 POP_JUMP_IF_FALSE       16 

 22          12 LOAD_CONST               2 (1) 
             15 RETURN_VALUE         

 23     >>   16 LOAD_CONST               3 (-1) 
             19 RETURN_VALUE         

8 lines, winner (by two lines that will never run)!

But this is definitely the premature optimisation your mother warned you about!

Sign up to request clarification or add additional context in comments.

4 Comments

As a question, is there some way in which the 3 methods are not equivalent in terms of possible outcomes in edge-cases that I'm not seeing? If not, why doesn't Python translate them into the same - um, whatever intermediate form Python translates into? I always thought if you did something like this on with a modern C compiler it was (theoretically) intelligent enough to compile functionally equivalent code into the same assembly code, and the fastest at that.
Premature optimization :') Haven't heard that in such a long time... 80-20 rule all the way! (20% of your code run 80% of the time; as long as they are fast, don't care about the rest).
A C compiler would probably optimize the hell out of that function, it would probably make it inline, even. But then Python is not C and isn't fast anyway, but the main point probably is that Python is either interpreted or JIT compiled at runtime. That doesn't leave too much time for heave optimization.
Thanks. I didn't know dis exist!
3

Here is my analysis:

A. Time complexity both are same :

func1          2011 function calls in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
     1000    0.000    0.000    0.001    0.000 fun.py:28(<lambda>)
     1000    0.000    0.000    0.000    0.000 fun.py:5(func1)
        1    0.000    0.000    0.000    0.000 timeit.py:143(setup)
        1    0.000    0.000    0.001    0.001 timeit.py:178(timeit)
        1    0.000    0.000    0.001    0.001 timeit.py:96(inner)
        1    0.000    0.000    0.000    0.000 {gc.disable}
        1    0.000    0.000    0.000    0.000 {gc.enable}
        1    0.000    0.000    0.000    0.000 {gc.isenabled}
        1    0.000    0.000    0.000    0.000 {globals}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        2    0.000    0.000    0.000    0.000 {time.time}


None
func2          2011 function calls in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
     1000    0.000    0.000    0.000    0.000 fun.py:13(func2)
     1000    0.000    0.000    0.001    0.000 fun.py:28(<lambda>)
        1    0.000    0.000    0.000    0.000 timeit.py:143(setup)
        1    0.000    0.000    0.001    0.001 timeit.py:178(timeit)
        1    0.000    0.000    0.001    0.001 timeit.py:96(inner)
        1    0.000    0.000    0.000    0.000 {gc.disable}
        1    0.000    0.000    0.000    0.000 {gc.enable}
        1    0.000    0.000    0.000    0.000 {gc.isenabled}
        1    0.000    0.000    0.000    0.000 {globals}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        2    0.000    0.000    0.000    0.000 {time.time}

B. Space: 2nd function is better:

Filename: fun.py

Line #    Mem usage    Increment   Line Contents
================================================
     4      8.2 MiB      0.0 MiB   @profile
     5                             def func1(a):
     6      8.2 MiB      0.0 MiB       if a > 0:
     7      8.2 MiB      0.0 MiB           num = 1
     8                                 else:
     9                                     num = -1
    10      8.2 MiB      0.0 MiB       return num


1
Filename: fun.py

Line #    Mem usage    Increment   Line Contents
================================================
    12      8.2 MiB      0.0 MiB   @profile
    13                             def func2(a):
    14      8.2 MiB      0.0 MiB       if a > 0:
    15      8.2 MiB      0.0 MiB           return 1
    16                                 else:
    17                                     return -1


1

C. Here is the bytecode by disassembling it:

func1
  6           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (0)
              6 COMPARE_OP               4 (>)
              9 POP_JUMP_IF_FALSE       21

  7          12 LOAD_CONST               2 (1)
             15 STORE_FAST               1 (num)
             18 JUMP_FORWARD             6 (to 27)

  9     >>   21 LOAD_CONST               3 (-1)
             24 STORE_FAST               1 (num)

 10     >>   27 LOAD_FAST                1 (num)
             30 RETURN_VALUE
None
func2
 14           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (0)
              6 COMPARE_OP               4 (>)
              9 POP_JUMP_IF_FALSE       16

 15          12 LOAD_CONST               2 (1)
             15 RETURN_VALUE

 17     >>   16 LOAD_CONST               3 (-1)
             19 RETURN_VALUE
             20 LOAD_CONST               0 (None)
             23 RETURN_VALUE
None

Conclusion: I will choose 2nd one.

1 Comment

I am using the following module : cProfile, Timer, dis and profile
2

There may be a minuscule difference in that the act of assigning a value to num is a step that can be bypassed by returning that value directly. I wouldn't recommend worrying about performance differences of this degree; instead, worry about code readability. I prefer the second example on readability grounds, but if you disagree, go with whichever you find is cleaner.

Comments

0

The second one is very slightly faster in that it does not create a new variable and then return that value later on. The return ends the function's control straightaway and returns a value without having to go through the rest of the function's code.

The first method however is slightly slower since it creates the variable num and then proceeds through more of the code until the final return value where num is referenced again and returned.

Although as @SukritKalra said, if this is bottlenecking your performance, props to you for a highly efficient script.

f2 --> Without num
f1 --> With num

f2(1) --> 4.812 e-06 seconds
f2(0) --> 4.812 e-06 seconds
f1(1) --> 6.256 e-06 seconds
f1(0) --> 4.812 e-06 seconds

Hardly any difference.

1 Comment

@Aशwiniचhaudhary, Right. Sorry. Mixed up the variables.

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.