Reputation: 2302
I was reading one article about list comprehensions and it mentions that list comprehensions are faster than regular and nested for loops. And while reading that article it mentions that this statement
if expression % 2 == 0:
slower than
if not expression % 2:
Can anybody explain reason behind this? Even I am solving trees problems and there is in recursion I am using not root kind of arguments. So that would be grateful if anybody can help.
Link to article: https://switowski.com/blog/for-loop-vs-list-comprehension Here is short implementation, thanks to @Ted
>>> MILLION_NUMBERS = list(range(1_000_000))
>>> def for_loop():
... output = []
... for element in MILLION_NUMBERS:
... if not element % 2:
... output.append(element)
... return output
...
>>> def for_loop2():
... output = []
... for element in MILLION_NUMBERS:
... if element % 2 == 0:
... output.append(element)
... return output
...
>>> timeit.timeit(stmt=for_loop, number=100)
6.254316797000001
>>> timeit.timeit(stmt=for_loop2, number=100)
7.362754617999997
Thanks in advance.
Upvotes: 2
Views: 642
Reputation: 6930
The principle reason is that Python does not know that expression
is an integer; potentially, it could be an object which overrides operators, so expression % 2 == 0
could mean calling expression.__mod__(2).__eq__(0).__bool__()
(or .__len__()
).
I'm not sure if Python actually calls them or optimises them away for integers, but either way it has to look them up, each time through the loop. Each of those method look-ups takes a tiny bit of time.
Meanwhile, not expression % 2
could at most be expression.__mod__(2).__bool__()
(or .__len__()
) — the look-up of ==
is skipped (there is no override for not
).
Upvotes: 1
Reputation: 397
If you use the disassembler on both the cases, you'll find:
if expression % 2 == 0:
2 0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (2)
4 BINARY_MODULO
6 LOAD_CONST 2 (0)
8 COMPARE_OP 2 (==)
10 POP_JUMP_IF_FALSE 20
if not expression % 2:
2 0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (2)
4 BINARY_MODULO
6 POP_JUMP_IF_TRUE 16
The above example has to do two more operations to determine if the value is zero. This was probably the basis the book author said that. In reality this barely makes any difference, and can vastly differ across implementations of python. You can also use the timeit module to time it!
>>> timeit.timeit('5 % 2 == 0', number=10**7)
0.47088638799323235
>>> timeit.timeit('not 5 % 2', number=10**7)
0.1560280679987045
which would show you the second one is almost 3x faster than the first one.
Upvotes: 4