我发现了一个有趣的性能优化。而不是使用all()
:
def matches(self, item):
return all(c.applies(item) for c in self.conditions)
我已经说过,当使用循环时它会更快:
def matches(self, item):
for condition in self.conditions:
if not condition.applies(item):
return False
return True
使用all()
,探查者会显示1160个额外的<genexpr>
来电:
4608 function calls (4600 primitive calls) in 0.015 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
580 0.002 0.000 0.008 0.000 rule.py:23(matches)
1160 0.002 0.000 0.005 0.000 rule.py:28(<genexpr>)
使用for
循环,没有<genexpr>
次呼叫:
2867 function calls (2859 primitive calls) in 0.012 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
580 0.002 0.000 0.006 0.000 rule.py:23(matches)
我的问题是差异来自哪里?我的第一个问题是all
评估了所有条件,但事实并非如此:
def foo():
print('foo')
return False
all(foo() for _ in range(1000))
foo
答案 0 :(得分:2)
要获取生成器中的下一个序列,将在生成器对象上调用next
,因此每次迭代都将进行此调用。 for循环不会产生这种成本,因此在这种情况下可能稍快一些。在这里,发电机在性能方面对你没有太多帮助。
一旦遇到假值,我的第一个是所有条件
all
就会停止。相反,any
在第一个truthy值上停止;这可能有助于更好地构建all
的含义。
考虑以下功能
def genfunc():
return all(i for i in range(1, 100))
def forfunc():
for i in range(1, 100):
if not i:
return False
return True
如果我们使用dis.dis
来查看正在发生的事情......
dis.dis(genfunc)
0 LOAD_GLOBAL 0 (all)
2 LOAD_CONST 1 (<code object <genexpr> at 0x04D4E5A0, file "<ipython-input-2-60c0c9eff4e2>", line 2>)
4 LOAD_CONST 2 ('genfunc.<locals>.<genexpr>')
6 MAKE_FUNCTION 0
8 LOAD_GLOBAL 1 (range)
10 LOAD_CONST 3 (1)
12 LOAD_CONST 4 (100)
14 CALL_FUNCTION 2
16 GET_ITER
18 CALL_FUNCTION 1
20 CALL_FUNCTION 1
22 RETURN_VALUE
和for循环版本..
dis.dis(forfunc)
0 SETUP_LOOP 26 (to 28)
2 LOAD_GLOBAL 0 (range)
4 LOAD_CONST 1 (1)
6 LOAD_CONST 2 (100)
8 CALL_FUNCTION 2
10 GET_ITER
>> 12 FOR_ITER 12 (to 26)
14 STORE_FAST 0 (i)
16 LOAD_FAST 0 (i)
18 POP_JUMP_IF_TRUE 12
20 LOAD_CONST 3 (False)
22 RETURN_VALUE
24 JUMP_ABSOLUTE 12
>> 26 POP_BLOCK
>> 28 LOAD_CONST 4 (True)
30 RETURN_VALUE
你会注意到在生成器表达式版本中有2个额外的函数调用(CALL_FUNCTION
)。这可以解释您在生成器expression.version中看到的额外1160个调用(每个580个循环有2个调用)。