![](/img/trans.png)
[英]Why this list comprehension is faster than equivalent generator expression?
[英]Python's [<generator expression>] at least 3x faster than list(<generator expression>)?
似乎在生成器表達式(test1)周圍使用[]表現得比將它放在list()(test2)中要好得多。 當我只是將列表傳遞給list()以進行淺拷貝(test3)時,速度就不存在了。 為什么是這樣?
證據:
from timeit import Timer
t1 = Timer("test1()", "from __main__ import test1")
t2 = Timer("test2()", "from __main__ import test2")
t3 = Timer("test3()", "from __main__ import test3")
x = [34534534, 23423523, 77645645, 345346]
def test1():
[e for e in x]
print t1.timeit()
#0.552290201187
def test2():
list(e for e in x)
print t2.timeit()
#2.38739395142
def test3():
list(x)
print t3.timeit()
#0.515818119049
機器:64位AMD,Ubuntu 8.04,Python 2.7(r27:82500)
好吧,我的第一步是獨立設置兩個測試,以確保這不是例如定義函數的順序的結果。
>python -mtimeit "x=[34534534, 23423523, 77645645, 345346]" "[e for e in x]"
1000000 loops, best of 3: 0.638 usec per loop
>python -mtimeit "x=[34534534, 23423523, 77645645, 345346]" "list(e for e in x)"
1000000 loops, best of 3: 1.72 usec per loop
果然,我可以復制這個。 好的,下一步是查看字節碼,看看實際發生了什么:
>>> import dis
>>> x=[34534534, 23423523, 77645645, 345346]
>>> dis.dis(lambda: [e for e in x])
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x0000000001F8B330, file "<stdin>", line 1>)
3 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (x)
9 GET_ITER
10 CALL_FUNCTION 1
13 RETURN_VALUE
>>> dis.dis(lambda: list(e for e in x))
1 0 LOAD_GLOBAL 0 (list)
3 LOAD_CONST 0 (<code object <genexpr> at 0x0000000001F8B9B0, file "<stdin>", line 1>)
6 MAKE_FUNCTION 0
9 LOAD_GLOBAL 1 (x)
12 GET_ITER
13 CALL_FUNCTION 1
16 CALL_FUNCTION 1
19 RETURN_VALUE
請注意,第一個方法直接創建列表,而第二個方法創建genexpr
對象並將其傳遞給全局list
。 這可能是開銷所在。
還要注意,差異大約是一微秒,即完全無關緊要。
這仍然適用於非平凡的列表
>python -mtimeit "x=range(100000)" "[e for e in x]"
100 loops, best of 3: 8.51 msec per loop
>python -mtimeit "x=range(100000)" "list(e for e in x)"
100 loops, best of 3: 11.8 msec per loop
對於不那么簡單的地圖功能:
>python -mtimeit "x=range(100000)" "[2*e for e in x]"
100 loops, best of 3: 12.8 msec per loop
>python -mtimeit "x=range(100000)" "list(2*e for e in x)"
100 loops, best of 3: 16.8 msec per loop
和(雖然不太強烈)如果我們過濾列表:
>python -mtimeit "x=range(100000)" "[e for e in x if e%2]"
100 loops, best of 3: 14 msec per loop
>python -mtimeit "x=range(100000)" "list(e for e in x if e%2)"
100 loops, best of 3: 16.5 msec per loop
list(e for e in x)
不是列表genexpr
,它是一個genexpr
對象(e for e in x)
被創建並傳遞給list
工廠函數。 據推測,對象創建和方法調用會產生開銷。
在python list
必須在模塊中查找名稱,然后在內置中查找。 雖然你不能改變列表理解意味着列表調用必須只是標准的查找+函數調用,因為它可以被重新定義為其他東西。
查看為理解而生成的vm代碼,可以看出,當列表調用是普通調用時,它是內聯的。
>>> import dis
>>> def foo():
... [x for x in xrange(4)]
...
>>> dis.dis(foo)
2 0 BUILD_LIST 0
3 DUP_TOP
4 STORE_FAST 0 (_[1])
7 LOAD_GLOBAL 0 (xrange)
10 LOAD_CONST 1 (4)
13 CALL_FUNCTION 1
16 GET_ITER
>> 17 FOR_ITER 13 (to 33)
20 STORE_FAST 1 (x)
23 LOAD_FAST 0 (_[1])
26 LOAD_FAST 1 (x)
29 LIST_APPEND
30 JUMP_ABSOLUTE 17
>> 33 DELETE_FAST 0 (_[1])
36 POP_TOP
37 LOAD_CONST 0 (None)
40 RETURN_VALUE
>>> def bar():
... list(x for x in xrange(4))
...
>>> dis.dis(bar)
2 0 LOAD_GLOBAL 0 (list)
3 LOAD_CONST 1 (<code object <genexpr> at 0x7fd1230cf468, file "<stdin>", line 2>)
6 MAKE_FUNCTION 0
9 LOAD_GLOBAL 1 (xrange)
12 LOAD_CONST 2 (4)
15 CALL_FUNCTION 1
18 GET_ITER
19 CALL_FUNCTION 1
22 CALL_FUNCTION 1
25 POP_TOP
26 LOAD_CONST 0 (None)
29 RETURN_VALUE
您的test2大致相當於:
def test2():
def local():
for i in x:
yield i
return list(local())
呼叫開銷解釋了增加的處理時間。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.