【问题标题】:Streams vs. tail recursion for iterative processes迭代过程的流与尾递归
【发布时间】:2011-12-22 10:46:34
【问题描述】:

这是my previous question 的后续行动。

我知道我们可以使用streams 来生成'pi'(和其他数字)的近似值、第n 个斐波那契等。但是我怀疑streams 是否是正确的方法来做到这一点。

主要缺点(如我所见)是内存消耗:例如stream 将保留 i drop,但这会使解决方案更加复杂。 tail recursion 看起来更适合处理此类任务。

你怎么看?

【问题讨论】:

    标签: scala stream tail-recursion


    【解决方案1】:

    如果需要快速行驶,请轻装上阵。这意味着;避免分配任何不必要的内存。如果您需要内存,请使用可用的 fastast 集合。如果你知道你需要多少内存;预分配。分配是绝对的性能杀手……用于计算。你的代码可能不再好看,但它会很快。

    但是,如果您使用 IO(磁盘、网络)或任何用户交互,那么分配就显得苍白了。那么最好将优先级从代码性能转移到可维护性。

    【讨论】:

      【解决方案2】:

      使用Iterator。它不保留中间值。

      【讨论】:

        【解决方案3】:

        如果您想要第 n 个斐波那契数并将流用作临时数据结构(如果您不保存对流的先前计算元素的引用),那么您的算法将在恒定空间中运行。 流的先前计算的元素(不再使用)将被垃圾收集。由于它们是在最年轻的一代中分配并立即收集的,因此几乎所有分配都可能在缓存中。

        更新:

        Stream 的当前实现似乎没有它可能的空间效率,主要是因为它从 LinearSeqOptimized trait 继承了 apply 方法的实现,它被定义为

        
        def apply(n: Int): A = {
           val rest = drop(n)
           if (n < 0 || rest.isEmpty) throw new IndexOutOfBoundsException("" + n)
           rest.head
        }
        
        this 持有对流头部的引用,并防止流被 gc'ed。因此drophead 方法的组合(如f.drop(100).head)对于删除中间结果是可行的情况可能更好。 (感谢 Sebastien Bocq 在 scala-user 上解释这些东西)。

        【讨论】:

        • 这不是真的,Streams 会永远保留所有生成的元素,就像 List 一样。对流的某些元素进行 GC 的唯一方法是使用 Stream.drop 获取新流并让旧流进行 GC。
        • 存在一个涉及流的恒定空间行为的错误,但现在已修复。 issues.scala-lang.org/browse/SI-692
        • 值得一提的是 Stream.from(0).foreach(_ =&gt; {})def s = Stream.from(0); s.foreach(_ =&gt; {}) 无限期运行,但 val s = Stream.from(0); s.foreach(_ =&gt; {}) 被 java.lang.OutOfMemoryError: Java heap space 杀死。
        • 好点,我主要考虑的是第二种情况,你可以引用头部并传递它。
        • 看来使用 Streams apply` 方法对这类东西有不足之处,我将在更新的答案中描述它。
        猜你喜欢
        • 2019-02-10
        • 2012-10-05
        • 2018-12-29
        • 1970-01-01
        • 2013-06-19
        • 2011-11-14
        • 2017-03-31
        • 2016-05-09
        • 2016-06-14
        相关资源
        最近更新 更多