当然,生成器表达式的循环比列表慢。但是在这种情况下,生成器内的迭代基本上是列表本身的循环,因此调用生成器的基本上委托给列表的next()
方法。
例如在这种情况下,没有2倍的性能差异。
>>> lst = list(range(10**5))
>>> %%timeit
... sum(x for x in lst)
...
100 loops, best of 3: 6.39 ms per loop
>>> %%timeit
... c = 0
... for x in lst: c += x
...
100 loops, best of 3: 6.69 ms per loop
首先,让我们检查这两个方法的字节码:
def gt_0(lst):
for elm in lst:
if elm > 0:
return True
return False
def any_with_ge(lst):
return any(elm > 0 for elm in lst)
字节码:
>>> dis.dis(gt_0)
10 0 SETUP_LOOP 30 (to 33)
3 LOAD_FAST 0 (lst)
6 GET_ITER
>> 7 FOR_ITER 22 (to 32)
10 STORE_FAST 1 (elm)
11 13 LOAD_FAST 1 (elm)
16 LOAD_CONST 1 (0)
19 COMPARE_OP 4 (>)
22 POP_JUMP_IF_FALSE 7
12 25 LOAD_GLOBAL 0 (True)
28 RETURN_VALUE
29 JUMP_ABSOLUTE 7
>> 32 POP_BLOCK
13 >> 33 LOAD_GLOBAL 1 (False)
36 RETURN_VALUE
>>> dis.dis(any_with_ge.func_code.co_consts[1])
17 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 17 (to 23)
6 STORE_FAST 1 (elm)
9 LOAD_FAST 1 (elm)
12 LOAD_CONST 0 (0)
15 COMPARE_OP 4 (>)
18 YIELD_VALUE
19 POP_TOP
20 JUMP_ABSOLUTE 3
>> 23 LOAD_CONST 1 (None)
26 RETURN_VALUE
正如你可以看到有一个在any()
版本没有跳转条件,它基本上获得了>
比较的值,然后再次检查它的真实性值再次使用PyObject_IsTrue
。另一方面,gt_0
检查一次条件的真值,并基于此返回True
或False
。
现在让我们添加另一个基于any()
的版本,该版本在for循环中具有if条件。
def any_with_ge_and_condition(lst):
return any(True for elm in lst if elm > 0)
字节码:
>>> dis.dis(any_with_ge_and_condition.func_code.co_consts[1])
21 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 23 (to 29)
6 STORE_FAST 1 (elm)
9 LOAD_FAST 1 (elm)
12 LOAD_CONST 0 (0)
15 COMPARE_OP 4 (>)
18 POP_JUMP_IF_FALSE 3
21 LOAD_GLOBAL 0 (True)
24 YIELD_VALUE
25 POP_TOP
26 JUMP_ABSOLUTE 3
>> 29 LOAD_CONST 1 (None)
32 RETURN_VALUE
现在,我们已经通过添加条件(查询详细最后一节)减少any()
所做的工作,它会在条件检查truthy两次只有一次将会是True
,否则它将基本上跳到下一个项目。
现在让我们来比较这三个时序:
>>> %timeit gt_0(lst)
10 loops, best of 3: 26.1 ms per loop
>>> %timeit any_with_ge(lst)
10 loops, best of 3: 57.7 ms per loop
>>> %timeit any_with_ge_and_condition(lst)
10 loops, best of 3: 26.8 ms per loop
让我们修改gt_0
包括两个检查是在简单any()
版本,并检查它的时机。
from operator import truth
# This calls `PyObject_IsTrue` internally
# https://github.com/python/cpython/blob/master/Modules/_operator.c#L30
def gt_0_truth(lst, truth=truth): # truth=truth to prevent global lookups
for elm in lst:
condition = elm > 0
if truth(condition):
return True
return False
时间:
>>> %timeit gt_0_truth(lst)
10 loops, best of 3: 56.6 ms per loop
现在,让我们看到,当我们尝试检查一个项目两次使用operator.truth
的truthy价值会发生什么。
>> %%timeit t=truth
... [t(i) for i in xrange(10**5)]
...
100 loops, best of 3: 5.45 ms per loop
>>> %%timeit t=truth
[t(t(i)) for i in xrange(10**5)]
...
100 loops, best of 3: 9.06 ms per loop
>>> %%timeit t=truth
[t(i) for i in xrange(10**6)]
...
10 loops, best of 3: 58.8 ms per loop
>>> %%timeit t=truth
[t(t(i)) for i in xrange(10**6)]
...
10 loops, best of 3: 87.8 ms per loop
这就是即使我们简单地调用truth()
相当差(即PyObject_IsTrue
)已经布尔对象上,我想那种解释的基本any()
版本缓慢。
你可能会说,if
条件any()
也将导致两个感实性检查,但情况并非如此,当comparison operation回报Py_True
或Py_False
。 POP_JUMP_IF_FALSE
只是跳到下一个OP代码,并且没有对PyObject_IsTrue
进行调用。
您是否尝试过使用不是以'0'开头的异构'lst'? –
一个更加等效的版本将是:'%timeit any(对于elm,如果elm> 0,则为真)'。 –
还有'any()'是在Python中的实际实现还是只是*等价的* Python语法? –