Say we have two pieces of code:
if foo == True and bar == False and baz == True:
do something
and
if foo == True:
if bar == False:
if baz == True:
do something
Which is faster?
On my machine with IPython
In [1]: foo = True
In [2]: bar = False
In [3]: baz = True
In [4]: %%timeit
...: if foo and not bar and baz:
...: lambda: None
1000000 loops, best of 3: 265 ns per loop
In [5]: %%timeit
...: if foo:
...: if not bar:
...: if baz:
...: lambda: None
1000000 loops, best of 3: 275 ns per loop
It looks like there is a whopping 10ns overhead if you split it up. If 10ns matters, you should probably be using another language.
So for all practical purposes, no, there is no difference.
If we look a little deeper, we can see where that tiny difference comes from though.
In [6]: def compound():
...: if foo and not bar and baz:
...: lambda: None
In [7]: def multiple():
....: if foo:
....: if not bar:
....: if baz:
....: lambda: None
In [8]: import dis
In [9]: dis.dis(compound)
2 0 LOAD_GLOBAL 0 (foo)
3 POP_JUMP_IF_FALSE 29
6 LOAD_GLOBAL 1 (bar)
9 UNARY_NOT
10 POP_JUMP_IF_FALSE 29
13 LOAD_GLOBAL 2 (baz)
16 POP_JUMP_IF_FALSE 29
3 19 LOAD_CONST 1 (<code object <lambda> at 0x101d953b0, file "<ipython-input-9-d057c552d038>", line 3>)
22 MAKE_FUNCTION 0
25 POP_TOP
26 JUMP_FORWARD 0 (to 29)
>> 29 LOAD_CONST 0 (None)
32 RETURN_VALUE
This has 13 instructions
In [15]: dis.dis(g)
2 0 LOAD_GLOBAL 0 (foo)
3 POP_JUMP_IF_FALSE 34
3 6 LOAD_GLOBAL 1 (bar)
9 POP_JUMP_IF_TRUE 34
4 12 LOAD_GLOBAL 2 (baz)
15 POP_JUMP_IF_FALSE 31
5 18 LOAD_CONST 1 (<code object <lambda> at 0x101dbb530, file "<ipython-input-10-32b41e5f6f2b>", line 5>)
21 MAKE_FUNCTION 0
24 POP_TOP
25 JUMP_ABSOLUTE 31
28 JUMP_ABSOLUTE 34
>> 31 JUMP_FORWARD 0 (to 34)
>> 34 LOAD_CONST 0 (None)
37 RETURN_VALUE
This has 14 instructions.
I did this with the default IPython on my system, which is 2.7.5 at the moment, but you can use this technique to profile pretty much anything you want with any version of Python you happen to be running.
Let's examine the bytecode and see!
>>> def f():
... if foo == True and bar == False and baz == True:
... pass
...
>>> def g():
... if foo == True:
... if bar == False:
... if baz == True:
... pass
...
>>> dis.dis(f)
2 0 LOAD_GLOBAL 0 (foo)
3 LOAD_GLOBAL 1 (True)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_FALSE 39
12 LOAD_GLOBAL 2 (bar)
15 LOAD_GLOBAL 3 (False)
18 COMPARE_OP 2 (==)
21 POP_JUMP_IF_FALSE 39
24 LOAD_GLOBAL 4 (baz)
27 LOAD_GLOBAL 1 (True)
30 COMPARE_OP 2 (==)
33 POP_JUMP_IF_FALSE 39
3 36 JUMP_FORWARD 0 (to 39)
>> 39 LOAD_CONST 0 (None)
42 RETURN_VALUE
>>> dis.dis(g)
2 0 LOAD_GLOBAL 0 (foo)
3 LOAD_GLOBAL 1 (True)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_FALSE 45
3 12 LOAD_GLOBAL 2 (bar)
15 LOAD_GLOBAL 3 (False)
18 COMPARE_OP 2 (==)
21 POP_JUMP_IF_FALSE 45
4 24 LOAD_GLOBAL 4 (baz)
27 LOAD_GLOBAL 1 (True)
30 COMPARE_OP 2 (==)
33 POP_JUMP_IF_FALSE 42
5 36 JUMP_ABSOLUTE 42
39 JUMP_ABSOLUTE 45
>> 42 JUMP_FORWARD 0 (to 45)
>> 45 LOAD_CONST 0 (None)
48 RETURN_VALUE
It's almost identical. The actual logic part is the exact same opcode sequence. It looks like the second version has very slightly less efficient jump targets, so g
might run very slightly slower, but this might change between Python versions and it almost always won't matter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With