【问题标题】:Would a StopIteration make python slow? [closed]StopIteration 会让 python 变慢吗? [关闭]
【发布时间】:2013-12-05 15:02:53
【问题描述】:

据我所知,监控异常会使程序变慢。

迭代器异常监视器(例如StopIteration)是否会使for 循环变慢?

【问题讨论】:

  • 我看不懂重点:这个StopIteration 应该来自哪里?通常,它由迭代器本身监控。
  • @glglgl 是的,它由迭代器监控..所以我不明白为什么迭代器不使用 hasext 函数而不是引发 StopIteration 异常?使用迭代器很常见,我认为它会大大提高性能
  • 与其他语言(如 C++)不同,使用异常的速度比常规代码慢一个数量级,Python 中的异常处理与任何其他代码的速度大致相同。我不会担心,除非您完成了分析,告诉您存在特定用途的问题。
  • @Blckknght 很酷,你介意分享一下 python 的异常处理速度如此之快的原因吗?

标签: python for-loop stopiteration


【解决方案1】:

虽然在通常情况下异常监视有一些小的开销,但在迭代器的情况下,处理StopIteration 异常似乎没有任何开销。 Python 将迭代器优化为一种特殊情况,以便StopIteration 不涉及任何异常处理程序。 (我还会观察到——我可能会遗漏一些东西——很难想出一个不隐式使用迭代器的 Python for 循环)。

这里有一些例子,首先使用内置的range函数和一个简单的for循环:

Python 2.7.5
>>> import dis
>>> def x():
...   for i in range(1,11):
...     pass
...
>>> dis.dis(x)
  2           0 SETUP_LOOP              23 (to 26)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1)
              9 LOAD_CONST               2 (11)
             12 CALL_FUNCTION            2
             15 GET_ITER
        >>   16 FOR_ITER                 6 (to 25)
             19 STORE_FAST               0 (i)

  3          22 JUMP_ABSOLUTE           16
        >>   25 POP_BLOCK
        >>   26 LOAD_CONST               0 (None)
             29 RETURN_VALUE

请注意,范围本质上被视为迭代器。

现在,使用一个简单的生成器函数:

>>> def g(x):
...   while x < 11:
...     yield x
...     x = x + 1
...
>>> def y():
...   for i in g(1):
...     pass
...
>>> dis.dis(y)
  2           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (g)
              6 LOAD_CONST               1 (1)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

  3          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE
>>> dis.dis(g)
  2           0 SETUP_LOOP              31 (to 34)
        >>    3 LOAD_FAST                0 (x)
              6 LOAD_CONST               1 (11)
              9 COMPARE_OP               0 (<)
             12 POP_JUMP_IF_FALSE       33

  3          15 LOAD_FAST                0 (x)
             18 YIELD_VALUE
             19 POP_TOP

  4          20 LOAD_FAST                0 (x)
             23 LOAD_CONST               2 (1)
             26 BINARY_ADD
             27 STORE_FAST               0 (x)
             30 JUMP_ABSOLUTE            3
        >>   33 POP_BLOCK
        >>   34 LOAD_CONST               0 (None)
             37 RETURN_VALUE

注意这里的y和上面的x基本相同,区别是一个LOAD_CONST指令,因为x引用了数字11。同样的,我们的简单生成器基本上等同于写相同的东西作为一个while循环:

>>> def q():
...   x = 1
...   while x < 11:
...     x = x + 1
...
>>> dis.dis(q)
  2           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (x)

  3           6 SETUP_LOOP              26 (to 35)
        >>    9 LOAD_FAST                0 (x)
             12 LOAD_CONST               2 (11)
             15 COMPARE_OP               0 (<)
             18 POP_JUMP_IF_FALSE       34

  4          21 LOAD_FAST                0 (x)
             24 LOAD_CONST               1 (1)
             27 BINARY_ADD
             28 STORE_FAST               0 (x)
             31 JUMP_ABSOLUTE            9
        >>   34 POP_BLOCK
        >>   35 LOAD_CONST               0 (None)
             38 RETURN_VALUE

同样,处理迭代器或生成器没有特定的开销(range 可能比生成器版本更优化,仅仅是因为它是内置的,而不是由于 Python 处理它的方式)。

最后,我们来看一个用StopIteration写的实际显式迭代器

>>> class G(object):
...   def __init__(self, x):
...     self.x = x
...   def __iter__(self):
...     return self
...   def next(self):
...     x = self.x
...     if x >= 11:
...       raise StopIteration
...     x = x + 1
...     return x - 1
...
>>> dis.dis(G.next)
  7           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (x)
              6 STORE_FAST               1 (x)

  8           9 LOAD_FAST                1 (x)
             12 LOAD_CONST               1 (11)
             15 COMPARE_OP               5 (>=)
             18 POP_JUMP_IF_FALSE       30

  9          21 LOAD_GLOBAL              1 (StopIteration)
             24 RAISE_VARARGS            1
             27 JUMP_FORWARD             0 (to 30)

 10     >>   30 LOAD_FAST                1 (x)
             33 LOAD_CONST               2 (1)
             36 BINARY_ADD
             37 STORE_FAST               1 (x)

 11          40 LOAD_FAST                1 (x)
             43 LOAD_CONST               2 (1)
             46 BINARY_SUBTRACT
             47 RETURN_VALUE

现在,在这里我们可以看到生成器函数涉及的指令比这个简单的迭代器少一些,主要与实现的差异有关,还有一些与引发StopIteration 异常有关的指令。然而,使用这个迭代器的函数完全等同于上面的y

>>> def z():
...   for i in G(1):
...     pass
...
>>> dis.dis(z)
  2           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (G)
              6 LOAD_CONST               1 (1)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

  3          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE

当然,这些结果是基于这样一个事实,即 Python for 循环将优化迭代器以消除对 StopIteration 异常的显式处理程序的需求。毕竟,StopIteration 异常本质上构成了 Python for 循环操作的正常部分。


关于为什么以这种方式实现,请参阅定义迭代器的PEP-234。这专门解决了异常费用的问题:

  • 有人质疑异常是否标志着结束 迭代并不太昂贵。的几种替代方案 已提出 StopIteration 异常:特殊值 End 表示结束,一个函数 end() 来测试迭代器是否 完成了,甚至重用了IndexError异常。

    • 一个特殊值的问题是,如果一个序列曾经 包含该特殊值,对该序列的循环将 在没有任何警告的情况下提前结束。如果有经验 以 null 结尾的 C 字符串并没有告诉我们这个问题 可能会导致,想象一下 Python 自省工具的麻烦 将遍历所有内置名称的列表, 假设特殊的 End 值是一个内置名称!

    • 调用 end() 函数需要每次调用两次 迭代。两个电话比一个电话贵得多 加上一个异常测试。尤其是时间紧迫的 for 循环可以非常便宜地测试异常。

    • 重用 IndexError 可能会导致混淆,因为它可能是 真正的错误,将通过结束循环来掩盖 过早的。

【讨论】:

  • 3x~这就是我想要的。
【解决方案2】:

查看由具有tryexcept 块的函数生成的字节码的输出,它看起来会稍微慢一些,但是,在大多数情况下这实际上可以忽略不计,因为它是 就性能影响而言,非常小。我认为在进行这样的优化时要考虑的真正事情是正确地界定异常。

编译为字节码时带有try/except块的示例函数的输出:

Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import dis
>>> def x():
    try:
        sd="lol"
    except:
        raise


>>> dis.dis(x)
  2           0 SETUP_EXCEPT            10 (to 13)

  3           3 LOAD_CONST               1 ('lol')
              6 STORE_FAST               0 (sd)
              9 POP_BLOCK           
             10 JUMP_FORWARD            10 (to 23)

  4     >>   13 POP_TOP             
             14 POP_TOP             
             15 POP_TOP             

  5          16 RAISE_VARARGS            0
             19 JUMP_FORWARD             1 (to 23)
             22 END_FINALLY         
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        
>>> 

【讨论】:

  • 是的,使用try..except会使程序变慢。为什么python使用raise StopIteration作为迭代器结束信号。为什么不只使用一个返回布尔值的函数hasext?
猜你喜欢
  • 2010-10-14
  • 2010-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-13
相关资源
最近更新 更多