I sometimes used map
if there was a function/method that was written in C to get a bit extra performance. However I recently revisited some of my benchmarks and noticed that the relative performance (compared to a similar list comprehension) drastically changed between Python 3.5 and 3.6.
That's not the actual code but just a minimal sample that illustrates the difference:
import random
lst = [random.randint(0, 10) for _ in range(100000)]
assert list(map((5).__lt__, lst)) == [5 < i for i in lst]
%timeit list(map((5).__lt__, lst))
%timeit [5 < i for i in lst]
I realize that it's not a good idea to use (5).__lt__
but I couldn't come up with a useful example right now.
The timings on Python-3.5 were in favor of the map
approach:
15.1 ms ± 5.64 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
16.7 ms ± 35.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
While the Python-3.6 timings actually show that the comprehension is faster:
17.9 ms ± 755 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
14.3 ms ± 128 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
My question is what happened in this case that made the list-comprehension faster and the map
solution slower? I realize the difference isn't that much, it just made me curious because that was one of the "tricks" I sometimes (actually seldom) used in performance critical codes.
I think a fair comparison involves using the same function and same testing conditions in Python 3.5 and 3.6 as well as when comparing
map
to list comprehension in a chosen Python version.In my initial answer I have performed multiple tests that showed that
map
was still faster by a factor of about two in both versions of Python when compared to list comprehension. However some results were not conclusive and so I performed some more tests.First let me cite some of your points stated in the question:
You also ask:
It is not very clear if you mean that map is slower than list comprehension in Python 3.6 or if you mean that map is slower in Python 3.6 than in 3.5 and list comprehension's performance has increased (albeit not necessarily to the level of beating
map
).Based on more extensive tests that I have performed after my first answer to this question, I think I have an idea of what is going on.
However, first let's create conditions for "fair" comparisons. For this we need to:
Compare performance of
map
in different Python versions using the same function;Compare performance of
map
to list comprehension in the same version using same function;Run the tests on same data;
Minimize contribution from timing functions.
Here is version information about my system:
and
Let's first address the issue of "same data". Unfortunately because you effectively are using
seed(None)
, each data setlst
is different on each of the two versions of Python. This probably contributes to the difference in performance seen on two Python versions. One fix would be to set, e.g.,random.seed(0)
(or something like that). I chose to create the list once and save it usingnumpy.save()
and then load it in each version. This is especially important because I chose to modify your tests slightly (number of "loops" and "repeats") and I have increased the length of your dataset to 100,000,000:Second, let's use
timeit
module instead of IPython's magic command%timeit
. The reason for doing this comes from the following test performed in Python 3.5:Compare this to the result of
timeit
in same version of Python:For unknown to me reasons, IPython's magic
%timeit
is adding some time compared totimeit
package. Therefore, I will usetimeit
exclusively in my testing.NOTE: In the discussions that follows I will use only minimum timing (
min(t)
).Tests in Python 3.5.3:
Group 1: map and list comprehension tests
Notice how second test (list comprehension using
f(i)
) is significantly slower than third test (list comprehension using5 < i
) indicating thatf = (5).__lt__
is not identical (or almost identical) to5 < i
from the code perspective.Group 2: "individual" function tests
Notice how again first test (of
f(1)
) is significantly slower than second test (of5 < 1
) further supporting thatf = (5).__lt__
is not identical (or almost identical) to5 < i
from the code perspective.Tests in Python 3.6.2:
Group 1: map and list comprehension tests
Group 2: "individual" function tests
Notice how again first test (of
f(1)
) is significantly slower than second test (of5 < 1
) further supporting thatf = (5).__lt__
is not identical (or almost identical) to5 < i
from the code perspective.Discussion
I do not know how reliable are these timing tests and it is also difficult to separate all factors that contribute to these timing results. However we can notice from the "Group 2" of tests that the only "individual" test that significantly changed its timing is the test of
5 < 1
: it went down to 0.0268s in Python 3.6 from 0.0309s in Python 3.5. This makes the list comprehension test in Python 3.6 that uses5 < i
to run faster than a similar test in Python 3.5. However, this does not mean that list comprehension become faster in Python 3.6.Let's compare relative performance of
map
to list comprehension for the same function in the same Python version. Then we get in Python 3.5:r(f) = 7.4428/4.6666 = 1.595
,r(abs) = 5.665/4.167 = 1.359
and in Python 3.6:r(f) = 7.316/4.5997 = 1.591
,r(abs) = 6.218/2.743 = 2.267
. Based on these relative performances we can see that in Python 3.6 performance of themap
relative to the performance of list comprehension is at least the same as in Python 3.5 for thef = (5).__lt__
function and this ratio has even improved for a function such asabs()
in Python 3.6.In any case, I believe that there is no evidence that list comprehension became faster in Python 3.6 neither in the relative nor in the absolute sense. The only performance improvement is for
[5 < i for i in lst]
test but that is because5 < i
itself became faster in Python 3.6 and not due to the list comprehension itself being faster.I think a fair comparison would involve using the same function. In the case of your example, when comparison is fair,
map
still wins:While in Python 3.5 (at least on my system)
map
is faster than in Python 3.6, so is list comprehension:Still, when using the same function,
map
is ~2x faster than list comprehension in both Python 3.5 and 3.6.EDIT (Response to @user2357112 comments):
It is my opinion that performing "fair" comparisons is important in answering OP's question: "My question is what happened in this case that made the list-comprehension faster and the map solution slower?" (last paragraph). However, in the first paragraph, @MSeifert says: "... [I] noticed that the relative performance (compared to a similar list comprehension) drastically changed between Python 3.5 and 3.6" That is, the comparison is between a
map
andlist comprehension
. Yet, @MSeifert tests are set-up as follows:This kind of testing makes it difficult to find the cause of timing differences: are they because list comprehension became faster in 3.6 or map became slower in 3.6 or
f(i)
is slower in 3.6 org(i)
is faster in 3.6...Therefore, I proposed to introduce
f = (5).__lt__
and use the same function in bothmap
and list comprehension tests. I have also modified @MSeifert test by increasing the number of elements in the list and redusing number of "loops" in thetimeit
:In Python 3.6 I get:
In Python 3.5 I get:
In my opinion this shows that list comprehension is slightly faster in 3.6 than in 3.5 except when
f
is used. Therefore, it is difficult to conclude that it is themap
that is slower in Python 3.6 or is firsttimeit
above is slower because of the call tof
being slower. Therefore I performed two more tests:In Python 3.6 I get:
In Python 3.5 I get:
This shows that
map
can be significantly faster than list comprehension for some functions: specifically, forabs(x)
relative performance ofmap
versus "list comprehension" in Python 3.6 is67.1/25.8 = 2.60
while in Python 3.5 it is56.4/38.3 = 1.47
. Therefore it is interesting to know why @MSeifert test shows thatmap
is slower in Python 3.6. My last test above shows timing test forf(1)
"alone". I am not sure how valid this test is (unfortunately) - I wanted to avoid usingmap
or[for]
to eliminate one variable - but it shows that in Python 3.6f = (5).__lt__
became slower than in Python 3.5. Therefore I conclude that it is the particular form of the functionf
((5).__lt__
) whose evaluation slowed and not themap
function. I know this last "alone" test is likely a bad test, however, the fact thatmap
is very fast (relatively or absolutely) when used withabs
shows that the problem is inf
and not inmap
.NOTE: Python 3.5 uses IPython 5.3.0 and Python 3.6 uses IPython 6.1.0.