为什么列表理解比附加到列表要快得多? [英] Why is a list comprehension so much faster than appending to a list?

查看:36
本文介绍了为什么列表理解比附加到列表要快得多?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道为什么列表理解比附加到列表要快得多.我认为差异只是表现力,但事实并非如此.

<预><代码>>>>导入时间>>>timeit.timeit(stmt=''' = []对于我在范围内(10000):t.append(i)''', number=10000)9.467898777974142>>>timeit.timeit(stmt='t= [i for i in range(10000)]', number=10000)4.1138417314859

列表理解速度提高了 50%.为什么?

解决方案

列表推导 基本上只是一个语法糖";对于常规 for 循环.在这种情况下,它表现更好的原因是它不需要加载列表的 append 属性并在每次迭代时将其作为函数调用.换句话说,一般来说,列表推导式执行得更快,因为暂停和恢复函数的框架,或其他情况下的多个函数,比按需创建列表慢.

考虑以下示例:

在 [1]: def f1():...: l = []...:对于范围内的我(5):...: l.append(i)...:...:...: def f2():...: [i for i in range(5)]...:在[3]中:导入dis在 [4] 中:dis.dis(f1)2 0 BUILD_LIST 02 STORE_FAST 0 (升)3 4 LOAD_GLOBAL 0(范围)6 LOAD_CONST 1 (5)8 CALL_FUNCTION 110 GET_ITER>>12 FOR_ITER 14(到 28)14 STORE_FAST 1 (i)4 16 LOAD_FAST 0 (升)18 LOAD_METHOD 1(附加)20 LOAD_FAST 1 (i)22 CALL_METHOD 124 POP_TOP26 JUMP_ABSOLUTE 12>>28 LOAD_CONST 0 (无)30 RETURN_VALUE在 [5]:在 [5] 中:dis.dis(f2)8 0 LOAD_CONST 1(<代码对象在0x7f397abc0d40,文件)2 LOAD_CONST 2 ('f2..')4 MAKE_FUNCTION 06 LOAD_GLOBAL 0(范围)8 LOAD_CONST 3 (5)10 CALL_FUNCTION 112 GET_ITER14 CALL_FUNCTION 116 POP_TOP18 LOAD_CONST 0 (无)20 RETURN_VALUE<代码对象<listcomp>的反汇编在 0x7f397abc0d40,文件<ipython-input-1-45c11e415ee9>",第 8 行>:8 0 BUILD_LIST 02 LOAD_FAST 0 (.0)>>4 FOR_ITER 8(到 14)6 STORE_FAST 1 (i)8 LOAD_FAST 1 (i)10 LIST_APPEND 212 JUMP_ABSOLUTE 4>>14 RETURN_VALUE在 [6] 中:

您可以看到,在第一个函数的偏移量 18 处,我们有一个 append 属性,而在使用列表推导式的第二个函数中没有这样的东西.所有这些额外的字节码都会使追加方法变慢,因为在这种情况下,您将在每次迭代中加载 append 属性,最终它将使代码仅使用列表推导式,其运行速度大约是第二个函数的两倍.

I was wondering why list comprehension is so much faster than appending to a list. I thought the difference is just expressive, but it's not.

>>> import timeit 
>>> timeit.timeit(stmt='''
t = []
for i in range(10000):
    t.append(i)''', number=10000)
9.467898777974142

>>> timeit.timeit(stmt='t= [i for i in range(10000)]', number=10000)
4.1138417314859

The list comprehension is 50% faster. Why?

解决方案

List comprehension is basically just a "syntactic sugar" for the regular for loop. In this case the reason that it performs better is because it doesn't need to load the append attribute of the list and call it as a function at each iteration. In other words and in general, list comprehensions perform faster because suspending and resuming a function's frame, or multiple functions in other cases, is slower than creating a list on demand.

Consider the following examples :

In [1]: def f1(): 
   ...:         l = [] 
   ...:         for i in range(5): 
   ...:             l.append(i) 
   ...:     
   ...:  
   ...: def f2(): 
   ...:     [i for i in range(5)] 
   ...:                                                                                                                                                                                                     

In [3]: import dis                                                                                                                                                                                          

In [4]: dis.dis(f1)                                                                                                                                                                                         
  2           0 BUILD_LIST               0
              2 STORE_FAST               0 (l)

  3           4 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (5)
              8 CALL_FUNCTION            1
             10 GET_ITER
        >>   12 FOR_ITER                14 (to 28)
             14 STORE_FAST               1 (i)

  4          16 LOAD_FAST                0 (l)
             18 LOAD_METHOD              1 (append)
             20 LOAD_FAST                1 (i)
             22 CALL_METHOD              1
             24 POP_TOP
             26 JUMP_ABSOLUTE           12
        >>   28 LOAD_CONST               0 (None)
             30 RETURN_VALUE

In [5]:                                                                                                                                                                                                     

In [5]: dis.dis(f2)                                                                                                                                                                                         
  8           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f397abc0d40, file "<ipython-input-1-45c11e415ee9>", line 8>)
              2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               3 (5)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 POP_TOP
             18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x7f397abc0d40, file "<ipython-input-1-45c11e415ee9>", line 8>:
  8           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (i)
              8 LOAD_FAST                1 (i)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE

In [6]:   

You can see that on offset 18 in the first function we have an append attribute while there's no such thing in second function using list comprehension. All those extra bytecodes will make the appending approach slower and since in this case you'll have loading of the append attribute in each iteration, in the end it will make the code to take approximately twice as slower as the second function using only list comprehension.

这篇关于为什么列表理解比附加到列表要快得多?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆