【问题标题】:Performance Advantages to Iterators?迭代器的性能优势?
【发布时间】:2010-10-12 08:23:33
【问题描述】:

使用迭代器可以提供哪些(如果有的话)性能优势。这似乎是解决许多问题的“正确方法”,但它是否创建了更快/更注重内存的代码?我正在专门考虑 Python,但不要将答案仅限于此。

【问题讨论】:

  • 你确定你的意思不是“发电机”吗?如果您实际上是指迭代器,那么避免它们的唯一方法是有一个 while 循环并手动增加索引变量,这......非常尴尬......
  • 我认为 OP 的意思是直接使用迭代器与将数据加载到列表中然后使用它的迭代器相比,性能优势是什么。

标签: python performance iterator


【解决方案1】:

在 python 邮件列表中实际上有一封非常好的邮件:Iterators vs Lists。它有点过时(从 2003 年开始),但据我所知,它仍然有效。

总结如下:

对于小型数据集,基于迭代器和列表的方法有相似之处 表现。 对于较大的数据集,迭代器既节省时间又节省空间。

我从中得出的结论是:如果可能的话,迭代器优先于将数据加载到列表中。但除非你有一个大数据集,否则不要扭曲你的代码来制作应该适合列表的东西以与迭代器一起使用。

【讨论】:

    【解决方案2】:

    迭代器速度更快,内存效率更高。想想range(1000)xrange(1000) 的例子。 (这在 3.0 中已更改,range 现在是一个迭代器。)使用 range 您可以预先构建您的列表,但 xrange 是一个迭代器,并在需要时产生下一个项目。

    在小事情上性能差异不是很大,但是一旦您开始处理它们并获得越来越多的信息集,您就会很快注意到它。此外,不仅需要生成然后逐步执行,您还将为预构建的项目消耗额外的内存,而使用迭代器,一次只能生成 1 个项目。

    【讨论】:

    • range 不是迭代器,它是可迭代的。为了证明这一点,试着做x = next(range(1000))。你会得到一个TypeError。您可以通过执行iter(range(1000))range 获取返回的迭代器。我认为您的意思是在 3.0 中 range 不再返回列表。当您对其进行迭代时,它一次返回一个项目。
    【解决方案3】:

    迭代器的主要好处不是性能之一。以我的经验,最高效的解决方案是创建一个嵌入您选择的数据结构的算法。迭代器的好处是它们允许您将数据和算法解耦,因此可以泛化和重用两者。如果这也可以在没有(或几乎没有)性能下降的情况下完成,那么这是一个净收益。

    我最喜欢的迭代器使用示例可以在 C++ Standard Template Library 中找到。它通过在不牺牲性能的情况下干净地分离容器和算法来设法展示抽象的力量和美感。理解这种设计对我思考代码的方式产生了深远的影响。

    【讨论】:

      【解决方案4】:

      备份@Christian Witts's answer

      rangexrange 性能对比

      python25 -mtimeit "for i in xrange(1000): pass"
      10000 loops, best of 3: 56.3 usec per loop
      
      python25 -mtimeit "for i in range(1000): pass"
      10000 loops, best of 3: 80.9 usec per loop
      
      python26 -mtimeit "for i in xrange(1000): pass"
      10000 loops, best of 3: 48.8 usec per loop
      
      python26 -mtimeit "for i in range(1000): pass"
      10000 loops, best of 3: 68.6 usec per loop
      

      顺便说一句,range()xrange() 都不是迭代器:

      >>> hasattr(range(1), 'next')
      False
      >>> hasattr(xrange(1), 'next')
      False
      >>> iter(xrange(1))
      <rangeiterator object at 0x0097A500>
      >>> iter(range(1))
      <listiterator object at 0x00A7BFD0>
      >>> iter([])
      <listiterator object at 0x00A7BE30>
      >>> iter(i for i in (1,))
      <generator object at 0x00A7F940>
      >>> (i for i in (1,))
      <generator object at 0x00A7FDC8>
      

      【讨论】:

      • 顺便说一句,python30 的答案是 31.5 微秒,不太适合您的比较,但很高兴知道,我认为
      • @SilentGhost:在 Python 3.x 中没有 xrange,因此没有什么可比的。
      • @SilentGhost:另外,除非您可以访问 J.F. Sebastian 的计算机,否则比较不是很有用..
      • 应该注意的是时间是微秒......你的代码中可能有更好的地方可以花时间优化(比如数据库访问)
      • @Jim:1. OP确实询问性能优势。 2.先测量,再优化(不要猜测是数据库访问,证明后才优化)。
      【解决方案5】:

      迭代器只是实现a particular interface 的类,专门用于进入下一个的接口。在 Python 中,列表、元组、字典、字符串和文件都实现了这个接口。如果它们实施不当,可能会导致性能不佳,但接口本身并没有暗示性能好坏。

      【讨论】:

      • 你所说的在技术上是正确的。但是,我不同意速度是底层数据结构的质量的结果。这更多地取决于数据结构是否适合该任务,或者是否真的需要。
      • 我的意思是,这与问题中所问的迭代器无关。使用迭代器,您调用 next() 直到引发 StopIteration。 next() 所做的是你的性能指标在哪里。最后,公认的答案是关于生成器,而不是迭代器,所以我想它没有实际意义。
      【解决方案6】:

      我从上面的许多答案中得出的结论是“使用列表编码。如有必要,使用迭代器重构”除非您拥有大型数据集,否则差异并不明显。

      另外需要注意的是,即使经常使用列表,我们操作的数据集也会越来越小。

      【讨论】:

        【解决方案7】:

        迭代器只是一个对象,它提供了允许遍历集合的方法。您可以使用相同的接口遍历数组的所有元素或树的所有节点。树和数组是非常不同的数据结构,需要不同的方法来遍历......但是使用迭代器,您可以以相同的方式遍历所有元素。

        对于一种类型的集合,也可能有不同的遍历方式,一个集合可以有多个迭代器。你可以有一个深度优先迭代器或广度优先迭代器遍历树结构并返回节点以不同的顺序。 迭代器的目的不是为了提高性能……而是通常用于为遍历结构提供一致的接口。

        【讨论】:

          【解决方案8】:

          我认为有一个答案有点混淆了生成器和迭代器的概念。所以我决定尝试用一个比喻的例子来回答这个问题。

          我在厨房工作,我的老板给我一个任务,将 10 个(或 100 个或一百万个)面包的重量相加。我有一个秤和一个计算器(我的算法的魔术)。下面是可迭代对象、生成器、迭代器、方法的区别:

          1. 可迭代对象: 每个面包都存放在一个盒子(内存)中,我称第一个(或第 0 个)面包称重,放下它的重量,然后把面包放回盒子里,然后去下一个,称重放回去,等等等等。最后,我得到了总重量,10个(100个或百万个)面包还在他们的盒子里。

          2. 生成器: 没有足够的盒子来存放所有这些面包,所以我请了面包师(发电机)的帮助,他制作了第一个面包,给我,我称重,把结果放下,把面包扔掉,然后不断地向他要另一个面包,等等,直到我得到最后一个面包(或者面包师的面粉用完了)。最后,我得到了结果,没有面包。但谁在乎呢,我的老板只让我称这些面包的重量,他并没有说我不能把它们扔掉(真是个聪明的服务生)。

          3. 迭代器: 我请某人(迭代器)帮我把第一个面包放到秤上,我称重,然后把结果记下来。这个人会去拿下一个来测量,不断地等等。我实际上不知道有人(迭代器)是从盒子里还是从面包师那里得到面包。最终,我得到了整体重量,这对我来说并不重要。

          总之,总结一下:

          1. 可迭代对象开始需要一些内存来存储数据。到最后,数据还在。

          2. 生成器开始时不需要内存来存储数据,它可以随时随地生成数据。

          3. 迭代器是算法与其数据之间的通道。该数据可能已经存在并存储在内存中,或者可能由生成器在运行中生成。在第一种情况下,随着迭代器不断迭代,该内存将被一点一点地释放。所以我非常同意上面的回答,即迭代器很好,因为它的抽象可以实现算法和数据的隔离。

          python 并不是这样工作的。希望它有助于澄清一点。

          【讨论】:

            【解决方案9】:

            稍微偏离主题,但总体上增加了列表在迭代器上的使用权重:使用迭代器更容易产生副作用,考虑一下:

            def foo(arg: Iterable[str]):
              print(list(arg)) # side effect: arg is exhausted at this point
              ...
            

            您可以说测试应该捕捉到这一点,但有时它不会。列表没有这个问题,因为它们是无状态的(在迭代的意义上)。

            【讨论】:

              猜你喜欢
              • 2010-10-20
              • 2011-03-21
              • 2022-01-21
              • 2016-02-08
              • 1970-01-01
              • 2016-09-26
              • 2015-01-06
              • 2012-07-13
              • 1970-01-01
              相关资源
              最近更新 更多