具有中断性能的all()vs for循环

时间:2018-03-30 14:22:44

标签: python python-3.x

我发现了一个有趣的性能优化。而不是使用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

1 个答案:

答案 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个调用)。