为什么列表内涵比列表更快?

我在想为什么列表内涵比列表快得多。我以为区别只是表现出来的,但其实不是。

>>> 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

列表内涵要快50% 为什么?

43744 次浏览

列表内涵 基本上只是常规 for循环的“语法糖”。在这种情况下,它之所以执行得更好,是因为它不需要加载列表的 append 属性,并在每次迭代时将其作为函数调用。换句话说,在 一般来说中,列表理解执行得更快,因为挂起和恢复一个函数的帧,或者其他情况下的多个函数,比按需创建列表要慢。

考虑以下例子:

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]:

你可以看到在第一个函数的偏移量18上有一个 append属性,而在第二个函数中没有使用列表内涵。所有这些额外的字节码都会使附加方法变慢,因为在这种情况下你会加载 append属性 在每次迭代中,最终它会使代码比只使用列表内涵的第二个函数慢大约两倍。

即使剔除了查找和加载 append函数所需的时间,这个列表内涵仍然更快,因为列表是用 C 语言创建的,而不是用 Python 一次构建一个条目。

# Slow
timeit.timeit(stmt='''
for i in range(10000):
t.append(i)''', setup='t=[]', number=10000)


# Faster
timeit.timeit(stmt='''
for i in range(10000):
l(i)''', setup='t=[]; l=t.append', number=10000)


# Faster still
timeit.timeit(stmt='t = [i for i in range(10000)]', number=10000)