Python的[<生成器表达式>]至少比list(<生成器表达式>)快3倍?
看起来,把生成器表达式放在方括号 [] 中(测试1)比放在 list() 函数里(测试2)要快很多。当我直接把一个列表传给 list() 函数做浅拷贝(测试3)时,速度也没有变慢。这是为什么呢?
证据:
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)
4 个回答
2
在Python中,list
这个名字需要先在模块中查找,然后再去内置的地方查找。虽然你不能改变列表推导式的意思,但调用列表的时候,它必须是一个标准的查找加上函数调用,因为它可能被重新定义成其他东西。
从生成的虚拟机代码来看,列表推导式是直接嵌入的,而调用列表则是一个普通的调用。
>>> 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
9
list(e for e in x)
其实不是一个列表推导式,而是一个生成器表达式 (e for e in x)
被创建出来,然后传给 list
函数。可以理解为,这样做会多一些额外的开销,因为要先创建这个对象再调用方法。
36
好的,我的第一步是把这两个测试独立设置起来,以确保这不是因为函数定义的顺序导致的结果。
>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