estani
estani

Reputation: 26487

Python performance of conditional evaluation

I was trying to find out if there's any penalty for negating a boolean when evaluation a conditional statement (python 2.6.6). I first tried this simple test (no else branch)

>>> import timeit
>>> timeit.timeit("if not True: pass", number=100000)
0.011913061141967773
>>> timeit.timeit("if True: pass", number=100000)
0.018882036209106445

So I though the results where skewed because the pass statement might be translated to a noop which is at least some operation.

I did a second try and got these results:

>>> timeit.timeit("a=False\nif not a: pass\nelse: pass", number=100000)
0.02387714385986328
>>> timeit.timeit("a=False\nif a: pass\nelse: pass", number=100000)
0.015386819839477539
>>> timeit.timeit("a=True\nif a: pass\nelse: pass", number=100000)
0.02389812469482422
>>> timeit.timeit("a=True\nif not a: pass\nelse: pass", number=100000)
0.015424966812133789

I didn't expect to see any large penalty but from this results it looks like evaluating the else branch is cheaper than the implicit then branch. And the difference is huge!

So The third attempt return these results:

>>> timeit.timeit("if True: a=1\nelse: a=1", number=100000)
0.022008895874023438
>>> timeit.timeit("if not True: a=1\nelse: a=1", number=100000)
0.022121906280517578

And finally I got the expected results. Though out of curiosity I tried a last time:

>>> timeit.timeit("if False: a=1\nelse: a=1", number=100000)
0.02385997772216797
>>> timeit.timeit("if not False: a=1\nelse: a=1", number=100000)
0.02244400978088379

And that's it... I have no idea why the negated condition leading to the then branch is faster.

What might be happening?

All those results are reproducible on my computer, it doesn't matter how many times I run them I get pretty much the same results.

I think the first tests where skewed because the compiler might have removed the else: pass part altogether. Is that possible?

Might all this results be related to the branch predictor in the CPU?

Any other possible culprits?

Upvotes: 3

Views: 1318

Answers (1)

jsbueno
jsbueno

Reputation: 110271

First, the "real world" news: if you really are in a situation where writing "if not" or "if ..pass else..." would impact the performance of your application, I'd suggest you did some serious profiling, and rewrite your inner loop in native code -either using Cython or C (and even other options, such as Fortran - Python is good at integrating).

Otherwise, I'd say these are irrelevant implementation details. Disassembling your code sequences with "dis" could show what is going on - but optimization at this level is of no pratical value in Python whatsoever - a single function call, even one implicit by using operators, would require about an extra order of magnitude of time than compared with the exection of the if statement.

Here:

>>> dis.dis(compile("if not True: pass", "None", "exec",))
  1           0 LOAD_NAME                0 (True)
              3 POP_JUMP_IF_TRUE         9
              6 JUMP_FORWARD             0 (to 9)
        >>    9 LOAD_CONST               0 (None)
             12 RETURN_VALUE        
>>> dis.dis(compile("if True: pass", "None", "exec",))
  1           0 LOAD_NAME                0 (True)
              3 POP_JUMP_IF_FALSE        9
              6 JUMP_FORWARD             0 (to 9)
        >>    9 LOAD_CONST               0 (None)
             12 RETURN_VALUE        
>>> 

As you can see, there is no difference in the bytecode generated for using "not" or not - but for the op of the jumping operator, which should take the same time in both cases.

Upvotes: 17

Related Questions