为什么流折叠操作抛出内存异常?

时间:2011-09-05 09:18:31

标签: scala scala-collections

我有以下简单的代码

 def fib(i:Long,j:Long):Stream[Long] = i #:: fib(j, i+j)
 (0l /: fib(1,1).take(10000000)) (_+_)

它会抛出OutOfMemmoryError异常。 我无法理解为什么,因为我认为所有部分都使用常量内存,即惰性评估流和foldLeft ......

这些代码也不起作用

fib(1,1).take(10000000).sum or max, min e.t.c.

如何正确实现无限流并对其进行迭代操作?

Scala版本:2.9.0

另外scala javadoc说,foldLeft操作对于流是记忆安全的

  /** Stream specialization of foldLeft which allows GC to collect
   *  along the way.
   */
  @tailrec
  override final def foldLeft[B](z: B)(op: (B, A) => B): B = {
    if (this.isEmpty) z
    else tail.foldLeft(op(z, head))(op)
  }

编辑:

使用迭代器的实现仍然无用,因为它会抛出$ {domainName}异常

   def fib(i:Long,j:Long): Iterator[Long] = Iterator(i) ++  fib(j, i + j)

如何在Scala中正确定义无限流/迭代器?

EDIT2: 我不关心int溢出,我只是想了解如何在scala中创建无限流/迭代器等而没有副作用。

5 个答案:

答案 0 :(得分:6)

使用Stream代替Iterator的原因是您无需再次计算系列中的所有小字词。但这意味着您需要存储一千万个流节点。不幸的是,它们非常大,因此可能足以溢出默认内存。解决这个问题的唯一现实方法是从更多内存开始(例如scala -J-Xmx2G)。 (另外,请注意,你将大幅度地溢出Long;斐波纳契系列增长得非常快。)

P.S。我想到的迭代器实现完全不同;你不是用连接的单例Iterator构建的:

def fib(i: Long, j: Long) = Iterator.iterate((i,j)){ case (a,b) => (b,a+b) }.map(_._1)

现在折叠时,可以放弃过去的结果。

答案 1 :(得分:3)

OutOfMemoryError独立于您使用Stream的事实发生。正如上面提到的Rex Kerr,Stream - 与Iterator不同 - 将所有内容存储在内存中。与List的不同之处在于,Stream的元素是懒惰计算的,但是一旦达到10000000,就会有10000000个元素,就像List一样。

尝试new Array[Int](10000000),你会遇到同样的问题。

要计算上面的斐波那契数,您可能需要使用不同的方法。您可以考虑到这样一个事实,即您只需要两个数字,而不是到目前为止发现的整个斐波那契数字。

例如:

scala> def fib(i:Long,j:Long): Iterator[Long] = Iterator(i) ++  fib(j, i + j)
fib: (i: Long,j: Long)Iterator[Long]

例如,第一个斐波那契数的索引超过1000000:

scala> fib(1, 1).indexWhere(_ > 1000000)
res12: Int = 30

编辑:我添加了以下几行来应对StackOverflow

如果你真的想使用第一百万个fibonacci数,那么上面的迭代器定义对StackOverflowError也不起作用。以下是我目前最好的想法:

  class FibIterator extends Iterator[BigDecimal] {
       var i: BigDecimal = 1
       var j: BigDecimal = 1
       def next = {val temp = i 
                   i = i + j
                   j = temp  
                   j }
       def hasNext = true
    }
scala> new FibIterator().take(1000000).foldLeft(0:BigDecimal)(_ + _)
res49: BigDecimal = 82742358764415552005488531917024390424162251704439978804028473661823057748584031
0652444660067860068576582339667553466723534958196114093963106431270812950808725232290398073106383520
9370070837993419439389400053162345760603732435980206131237515815087375786729469542122086546698588361
1918333940290120089979292470743729680266332315132001038214604422938050077278662240891771323175496710
6543809955073045938575199742538064756142664237279428808177636434609546136862690895665103636058513818
5599492335097606599062280930533577747023889877591518250849190138449610994983754112730003192861138966
1418736269315695488126272680440194742866966916767696600932919528743675517065891097024715258730309025
7920682881137637647091134870921415447854373518256370737719553266719856028732647721347048627996967...

答案 2 :(得分:3)

@ yura的问题:

def fib(i:Long,j:Long):Stream[Long] = i #:: fib(j, i+j)
(0l /: fib(1,1).take(10000000)) (_+_)

除了使用不能容纳10,000,000的Fibonacci的Long之外,它确实有用。也就是说,如果foldLeft写为:

fib(1,1).take(10000000).foldLeft(0L)(_+_)

查看Streams.scala来源,foldLeft()显然是为垃圾收集而设计的,但/:未定义。

其他答案提到另一个问题。 1000万的Fibonacci是一个很大的数字,如果使用BigInt,而不是像Long一样溢出,那么一次又一次地添加绝对庞大的数字。

由于Stream.foldLeft针对GC进行了优化,因此它看起来像是解决非常大的Fibonacci数的方法,而不是使用zip或尾递归。

// Fibonacci using BigInt
def fib(i:BigInt,j:BigInt):Stream[BigInt] = i #:: fib(j, i+j)
fib(1,0).take(10000000).foldLeft(BigInt("0"))(_+_)

上述代码的结果:10,000,000是一个8位数字。 fib中有多少个数字(10000000)? 2,089,877

答案 3 :(得分:1)

fib(1,1).take(10000000)是方法this的“/:”,只要方法运行,JVM就可能会将引用视为有效,即使在这种情况下,它可能会摆脱它。 因此,您始终在流的头部保留一个引用,因此在构建到10M元素时将整个流引用。

答案 4 :(得分:1)

你可以使用递归,这很简单:

def fibSum(terms: Int, i: Long = 1, j: Long = 1, total: Long = 2): Long = {
  if (terms == 2) total
  else fibSum(terms - 1, j, i + j, total + i + j)
}

有了这个,你可以在几秒内“折叠”十亿个元素,但正如雷克斯指出的那样,对Fibbonaci序列的求和非常快。

如果您真的想知道原始问题的答案,并且不介意牺牲一些准确性,那么您可以这样做:

def fibSum(terms: Int, i: Double = 1, j: Double = 1, tot: Double = 2,
                                                     exp: Int = 0): String = {
  if (terms == 2) "%.6f".format(tot) + " E+" + exp
  else {
    val (i1, j1, tot1, exp1) =
      if (tot + i + j > 10) (i/10, j/10, tot/10, exp + 1) 
      else                  (i, j, tot, exp)
    fibSum(terms - 1, j1, i1 + j1, tot1 + i1 + j1, exp1)
  }
}

scala> fibSum(10000000)
res54: String = 2.957945 E+2089876