Which One Use: List Comprehension Or Map Part 2?

published at 07 Oct, 2016 by Szymon LipiƄski tags: python programming

In the previous blog post I have presented results of benchmarking list comprehensions and maps. In this one I will run similar tests, but I will filter the list to use only the even numbers.

I will not repeat the test description from the previous article, you can find it there.

The Test Logic

The test logic is simple: sum all the squares of the even number from the range of (1..max). So basically:

data = list(range(1, MAX_NUMBER+1))

@profile
def sum_numbers(data):
    res = 0
    # different algorithms go here
    return res

A couple of remarks:

The original list contains the same number of elements as in the previous post. I will just filter it to use only the even ones.

Filtering will be done using normal if or if in list comprehension or filter() when using map().

The filtering expression is: number % 2 == 0.

For Python 2 I used: 2.7.12 (default, Jul 1 2016, 15:12:24) [GCC 5.4.0 20160609].

For Python 3 I used: 3.5.2 (default, Sep 10 2016, 08:21:44) [GCC 5.4.0 20160609].

I will not present the full results, instead there is just a table at the end.

A Simple For Loop

This is the simplest and the most common way of iterating a container. Just make a for loop.

@profile
def sum_numbers(data):
    res = 0
    for x in data:
        if x % 2 == 0:
            res += x*x
    return res

A List Comprehension Squared With For Loop

Here we have a list comprehension, which creates a list of original values. The values are then squared and summed.

@profile
def sum_numbers_list_comprehension_for_square(data):
    res = 0
    for x in [n for n in data if n % 2 == 0]:
        res += x*x
    return res

A List Comprehension With For Loop Squaring

Here we have a list comprehension, which creates a list of squared values, and then it is summed using the for loop.

@profile
def sum_numbers_list_comprehension_for_square(data):
    res = 0
    for x in [n*n for n in data if n % 2 == 0]:
        res += x
    return res

A List Comprehension With Sum()

There is a list comprehension, which creates a list of squared values, and then it is summed using the sum function.

@profile
def sum_numbers_list_comprehension_squared_sum(data):
    res = 0
    res = sum([n*n for n in data if n % 2 == 0])
    return res

A Map With For Loop Squaring

Here we have the map() function, which returns in fact an original values. They are then squared, and summed in a for loop.

@profile
def sum_numbers_map_for_square(data):
    res = 0
    for x in map(lambda n: n, filter(lambda m: m % 2 == 0, data)):
        res += x*x
    return res

A Map Squared With For Loop

Here the map returns squared values, which are summed using the for loop.

@profile
def sum_numbers_map_squared_for(data):
    res = 0
    for x in map(lambda n: n*n, filter(lambda m: m % 2 == 0, data)):
        res += x
    return res

A Map Squared With Sum

And the most common functional way of implementing this: map returning squared values, which are summed using the sum function.

@profile
def sum_numbers_map_squared_sum(data):
    res = 0
    res = sum(map(lambda n: n*n, filter(lambda m: m % 2 == 0, data)))
    return res

The Summary

Lot’s of data. Let’s sum it up.

The data from the previous post (so all functions without filtering):

Test Name Python Version Time [s] Memory Jump [MB]
simple for loop 2 68.41 0.0
simple for loop 3 76.08 0.0
compr. for sq. 2 105.65 9.7
compr. for sq. 3 117.45 7.8
compr. sq. for 2 105.33 9.2
compr. sq. for 3 117.23 7.8
compr. sum 2 34.68 41.6
compr. sum 3 37.74 38.8
map for. sq. 2 139.67 7.6
map for. sq. 3 150.95 0.0
map sq. for 2 141.77 31.1
map sq. for 3 159.23 0.0
map sq. sum 2 71.23 31.1
map sq. sum 3 77.07 0.0

This time data:

Test Name Python Version Time [s] Memory Jump [MB]
simple for loop 2 86.43 0.0
simple for loop 3 95.12 0.0
compr. for sq. 2 70.00 3.7
compr. for sq. 3 76.40 3.9
compr. sq. for 2 70.21 17.0
compr. sq. for 3 77.28 19.5
compr. sum 2 35.70 17.0
compr. sum 3 38.39 19.4
map for. sq. 2 146.33 11.5
map for. sq. 3 155.91 0.0
map sq. for 2 138.70 23.3
map sq. for 3 156.66 0.0
map sq. sum 2 109.05 8.6
map sq. sum 3 119.87 0.0

I’m interested only in the memory jump inside the test function. I ignore it if the memory decreased later.

A couple of remarks:

  • The simple for loop is not the best.
  • The memory overhead is much smaller than without filtering, which is not a surprise, as we need to store half of the data.
  • Map is still much slower than list comprehension.
  • Map on Python 3 has almost no overhead - which is obvious when you read the documentation and find out that the filter and the map are generators, so they return one value at a time instead of building the whole list.
  • The list comprehension has memory overhead, as it builds a new list, so generally it should be slower, and has bigger memory usage.
  • Python 3 is slower than Python 2.

The Final Remark

After all the benchmarks I found out one thing.

That’s not true that

You should use list comprehension because that’s more pythonic.

The truth is

You should use list comprehensions, not map, because they are MUCH FASTER

Howgh