Reputation: 26487
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
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