performance - Why list comprehension can be faster than map() in Python? -


i looking in performance issues of loop structures in python , found following statements:

besides syntactic benefit of list comprehensions, fast or faster equivalent use of map. (performance tips)

list comprehensions run bit faster equivalent for-loops (unless you're going throw away result). (python speed)

i wondering difference under hood gives list comprehension advantage. thanks.

test one: throwing away result.

here's our dummy function:

def examplefunc(x):     pass 

and here our challengers:

def listcomp_throwaway():     [examplefunc(i) in range(100)]  def forloop_throwaway():     in range(100):         examplefunc(i) 

i won't analysis of raw speed, why, per op's question. lets take @ diffs of machine code.

--- list comprehension +++ loop @@ -1,15 +1,16 @@ - 55           0 build_list               0 + 59           0 setup_loop              30 (to 33)                3 load_global              0 (range)                6 load_const               1 (100)                9 call_function            1               12 get_iter             -        >>   13 for_iter                18 (to 34) +        >>   13 for_iter                16 (to 32)               16 store_fast               0 (i) -             19 load_global              1 (examplefunc) + + 60          19 load_global              1 (examplefunc)               22 load_fast                0 (i)               25 call_function            1 -             28 list_append              2 -             31 jump_absolute           13 -        >>   34 pop_top              -             35 load_const               0 (none) -             38 return_value         +             28 pop_top              +             29 jump_absolute           13 +        >>   32 pop_block            +        >>   33 load_const               0 (none) +             36 return_value      

the race on. listcomp's first move build empty list, while loop's setup loop. both of them proceed load global range(), constant 100, , call range function generator. both current iterator , next item, , store variable i. load examplefunc , , call examplefunc. listcomp appends list , starts loop on again. loop same in 3 instructions instead of two. both load none , return it.

so seems better in analysis? here, list comprehension redundant operations such building list , appending it, if don't care result. loop pretty efficient too.

if time them, using loop one-third faster list comprehension. (in test, examplefunc divided argument 5 , threw away instead of doing nothing @ all.)

test two: keeping result normal.

no dummy function test. here our challengers:

def listcomp_normal():     l = [i*5 in range(100)]   def forloop_normal():     l = []     in range(100):         l.append(i*5) 

the diff isn't use today. it's 2 machine codes in 2 blocks.

list comp's machine code:

 55           0 build_list               0               3 load_global              0 (range)               6 load_const               1 (100)               9 call_function            1              12 get_iter                     >>   13 for_iter                16 (to 32)              16 store_fast               0 (i)              19 load_fast                0 (i)              22 load_const               2 (5)              25 binary_multiply                   26 list_append              2              29 jump_absolute           13         >>   32 store_fast               1 (l)              35 load_const               0 (none)              38 return_value         

for loop's machine code:

 59           0 build_list               0               3 store_fast               0 (l)   60           6 setup_loop              37 (to 46)               9 load_global              0 (range)              12 load_const               1 (100)              15 call_function            1              18 get_iter                     >>   19 for_iter                23 (to 45)              22 store_fast               1 (i)   61          25 load_fast                0 (l)              28 load_attr                1 (append)              31 load_fast                1 (i)              34 load_const               2 (5)              37 binary_multiply                   38 call_function            1              41 pop_top                           42 jump_absolute           19         >>   45 pop_block                    >>   46 load_const               0 (none)              49 return_value         

as can tell, list comprehension has fewer instructions loop does.

list comprehension's checklist:

  1. build anonymous empty list.
  2. load range.
  3. load 100.
  4. call range.
  5. get iterator.
  6. get next item on iterator.
  7. store item onto i.
  8. load i.
  9. load integer five.
  10. multiply times five.
  11. append list.
  12. repeat steps 6-10 until range empty.
  13. point l anonymous empty list.

for loop's checklist:

  1. build anonymous empty list.
  2. point l anonymous empty list.
  3. setup loop.
  4. load range.
  5. load 100.
  6. call range.
  7. get iterator.
  8. get next item on iterator.
  9. store item onto i.
  10. load list l.
  11. load attribute append on list.
  12. load i.
  13. load integer five.
  14. multiply times five.
  15. call append.
  16. go top.
  17. go absolute.

(not including these steps: load none, return it.)

the list comprehension doesn't have these things:

  • load append of list every time, since it's pre-bound local variable.
  • load i twice per loop
  • spend 2 instructions going top
  • directly append list instead of calling wrapper appens list

in conclusion, listcomp lot faster if going use values, if don't it's pretty slow.

real speeds

test one: loop faster one-third*

test two: list comprehension faster two-thirds*

*about -> second decimal place acurrate


Comments