【问题标题】:all() vs for loop with break performanceall() 与具有中断性能的 for 循环
【发布时间】:2018-09-09 14:22:06
【问题描述】:

我发现了一个有趣的性能优化。而不是使用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 循环,没有&lt;genexpr&gt; 调用:

         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

【问题讨论】:

标签: python python-3.x


【解决方案1】:

为了在生成器中获取下一个序列,在生成器对象上调用next,因此每次迭代都会有这个调用。 for 循环不会产生此成本,因此在这种情况下可能会稍快一些。在这里,生成器不会在性能方面为您做任何事情。

我的第一个想法是 all 评估所有条件

all 将在遇到虚假值时立即停止。相反,any 停在第一个真值上;这可能有助于更好地理解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 次调用)。

【讨论】:

  • 是的,但是。这取决于你如何称呼所有。例如,all([a(), b(), c()] 将首先计算a()b()c(),然后将真实性列表传递给 all()。这可能比最初计算为假的写出 for 循环更昂贵。
  • 是的,惰性求值是您真正获得生成器性能优势的一种方式。但是,在这种情况下,它不是这样使用的。
猜你喜欢
  • 2015-08-28
  • 2017-03-01
  • 2011-04-28
  • 2012-11-18
  • 1970-01-01
  • 2017-09-19
  • 2012-08-06
  • 1970-01-01
  • 2010-11-13
相关资源
最近更新 更多