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

I am looking into performance issues of a loop type structure in Python and found the following statements:

Aside from the syntactic advantage of comprehending lists, they are often as fast or faster than the equivalent use of a map. ( Performance Tips )

Explanations of lists run a bit faster than their equivalent for loops (except you're just going to throw away the result). ( Python Speed )

I'm wondering what the difference under the hood makes it possible to understand this advantage. Thank.

+3


source to share


1 answer


Check one : discard the result.

Here's our dummy function:

def examplefunc(x):
    pass

      

And here are our rivals:

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

def forloop_throwaway():
    for i in range(100):
        examplefunc(i)

      

I won't analyze my raw speed, just why, on the OP question. Let's look at the difference in machine code.

--- List comprehension
+++ For 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 continues. Listcomp's first attempt is to create an empty list, and for a loop, setting the loop. They then go to the global range () constant 100 and call the range function on the generator. Then they both get the current iterator and get the next element and store it in the i variable. Then they load examplefunc and me and call examplefunc. Listcomp adds it to the list and starts the loop again. For a loop, the same is done in three commands instead of two. Then they both load None and return it.

So who is better at this analysis? Here, a list comprehension does some redundant operations like creating a list and adding to it if you don't care about the results. Pretty effective for a loop.

If you use them, using a for loop is about a third faster than a list comprehension. (In this test, examplefunc split its argument by five and discarded it instead of doing nothing).

Check two : save the result as normal.

No dummy function validates this test. So here are our contenders:

def listcomp_normal():
    l = [i*5 for i in range(100)]


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

      

This is not a problem for us today. It is just two machine codes in two blocks.

List of machine code for the list:

 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 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 you can probably already tell, there are fewer instructions in list comprehension than for loop.

Help List Checklist:

  • Create an anonymous empty list.
  • Download range

    .
  • Download 100

    .
  • Challenge range

    .
  • Get an iterator.
  • Get the next item in this iterator.
  • Save this item to i

    .
  • Download i

    .
  • Load integer five.
  • Multiply five times.
  • Add a list.
  • Repeat steps 6-10 until the range is empty.
  • Point l

    to an anonymous empty list.

Cycle checklist:

  • Create an anonymous empty list.
  • Point l

    to an anonymous empty list.
  • Cycle setup.
  • Download range

    .
  • Download 100

    .
  • Challenge range

    .
  • Get an iterator.
  • Get the next item in this iterator.
  • Save this item to i

    .
  • Download the list l

    .
  • Load an attribute into this list append

    .
  • Download i

    .
  • Load integer five.
  • Multiply five times.
  • Challenge append

    .
  • Go to the top of the page.
  • Go to the absolute.

(Apart from these steps: Download None

, bring it back.)

Understanding the list does not have to do the following:

  • Load adding the list every time as it is pre-bound as a local variable.
  • Loading i

    twice per cycle
  • Swipe the two instructions going to the beginning
  • Add directly to the list instead of calling the wrapper that adds the list

In conclusion, listcomp is much faster if you are going to use values, but if you are not doing it quite slow.

Real speeds

Check one: for a cycle faster by about one third *

Check two: list comprehension is about two-thirds complete *

* O → second after decimal point.

+6


source







All Articles