shouldn't list comprehension be faster than for loops?
Steven D'Aprano
steve at REMOVE-THIS-cybersource.com.au
Sat Dec 19 03:04:55 EST 2009
On Sat, 19 Dec 2009 12:28:32 +1100, Ryan Kelly wrote:
>> Anything else being equal, list comprehensions will be the faster
>> becuase they incur fewer name and attribute lookups. It will be the
>> same as the difference between a for loop and a call to map. A list
>> comprehension is basically an enhancement of map.
>
> Not so. If you use the "dis" module to peek at the bytecode generated
> for a list comprehension, you'll see it's very similar to that generated
> for an explicit for-loop. The byte-code for a call to map is very
> different.
"Very similar" and "very different" byte-code mean very little regarding
speed.
> Basically: both a for-loop and a list-comp do the looping in python
> bytecode, while a call to map will do the actual looping in C.
This is a classic example of the confirmation fallacy -- if you say that
for-loops and list-comps are very similar, you need to actually check the
byte-code of both. You don't. You need to compare the byte-code of all
three operations, not just two of them, e.g.:
dis.dis(compile("map(f, seq)", '', 'exec'))
dis.dis(compile("[f(x) for x in seq]", '', 'exec'))
dis.dis(compile("L = []\nfor x in seq: L.append(f(x))", '', 'exec'))
But in fact just looking at the byte-code isn't helpful, because it tells
you nothing about the relative speed of each operation. You need to
actually time the operations.
>>> from timeit import Timer
>>> t1 = Timer("map(len, 'abcdefgh')", setup='')
>>> t2 = Timer("[len(c) for c in 'abcdefgh']", setup='')
>>> t3 = Timer("""L = []
... for c in 'abcdefgh':
... L.append(len(c))
... """, setup='')
>>>
>>> min(t1.repeat())
3.9076540470123291
>>> min(t2.repeat())
4.5931642055511475
>>> min(t3.repeat())
7.4744069576263428
So, on my PC, with Python 2.5, with this example, a for-loop is about 60%
slower than a list comp and about 90% slower than map; the list comp is
about 20% slower than map.
But that only holds for *that* example. Here's another one:
>>> def f(x):
... return 1+2*x+3*x**2
...
>>> values = [1,2,3,4,5,6]
>>> t1 = Timer("map(f, values)", setup='from __main__ import f, values')
>>> t2 = Timer("[f(x) for x in values]",
... setup='from __main__ import f, values')
>>>
>>> t3 = Timer("""L = []
... for x in values:
... L.append(f(x))
... """, setup='from __main__ import f, values')
>>>
>>> min(t1.repeat())
7.0339860916137695
>>> min(t2.repeat())
6.8053178787231445
>>> min(t3.repeat())
9.1957418918609619
For this example map and the list comp are nearly the same speed, with
map slightly slower; but the for-loop is still significantly worse.
Of course, none of these timing tests are terribly significant. The
actual difference in time is of the order of a millionth of a second per
call to map compared to the list comp or the for-loop, for these small
examples. Most of the time you shouldn't care about time differences of
that magnitude, and write whatever is easiest.
--
Steven
More information about the Python-list
mailing list