带流处理的OutOfMemoryException

时间:2014-03-21 01:20:30

标签: scala stream

我写了一个函数,给定Stream[Long],它会过滤掉数字> 4000000。

  def filterLt4Mil(xs: Stream[Long]) = {
    @tailrec
    def go(xs: Stream[Long], acc: Stream[Long]): Stream[Long] = xs match {
      case Stream() => acc
      case a #:: as if(a < 4000000L) => go(as, acc :+ a)
      case a #:: as if(a > 4000000L) => go(as, acc)
    }
    go(xs, Stream[Long]())
  }

然而,当我传入0到10,000,000,000的流时,我得到OutOfMemoryException

scala> val x = Stream.range(0,10000000L)
x: scala.collection.immutable.Stream[Long] = Stream(0, ?)

scala> filterLt4Mil(x)
java.lang.OutOfMemoryError: GC overhead limit exceeded
        at scala.collection.immutable.List.toStream(List.scala:312)

由于filterLt4Mil使用尾调用优化,我的理解是堆栈不应该溢出。

但是,为什么会出现OutOfMemoryException?我该如何预防?

3 个答案:

答案 0 :(得分:0)

您的功能存在一些问题。

您的go函数具有反转输出流中元素的效果,因为您将附加到累加器的末尾。

此外,附加行为而不是前置行为会导致整个累加器具体化为List,然后在迭代的每一步复制。这就是造成OutOfMemoryError的原因,因为你的实现需要二次内存。

此外,由于您未处理match的情况,因此a == 4000000L阻止并非详尽无遗。

如果您只想过滤掉大于4000000的元素,这就足够了:

xs filterNot {_ > 4000000L}

答案 1 :(得分:0)

嗯,已经有一个filter方法用于流,但我想你想自己做。

溪流有严格的头部和懒惰的尾巴。调用Stream.range(0,10000000L)只会在内存中有0,因为其余内容会被懒惰地评估。

但是在您的filterLt4Mil方法中,您遍历整个流,导致评估此流中的所有元素,从而导致从010000000L的所有数字都存储在内存中... 存储此类金额可能会导致OutOfMemoryException

例如:x.forall(_ >= 0)也会产生OutOfMemoryExceptionforall也必须在_ >= 0满足时遍历整个流。

您希望懒惰地应用filter方法。看看源代码。可能会让你知道它是如何工作的: (http://harrah.github.io/browse/samples/library/scala/collection/immutable/Stream.scala.html

override final def filter(p: A => Boolean): Stream[A] = {
  // optimization: drop leading prefix of elems for which f returns false
  var rest = this dropWhile (!p(_))
  if (rest.isEmpty) Stream.Empty
  else new Stream.Cons(rest.head, rest.tail filter p)
}

还有一个关于代码的问题。由于它必须遍历整个流,您可以猜测无限流会发生什么。

答案 2 :(得分:0)

我认为你没有吹嘘堆栈 - 看起来你已经没有堆了(确认,切换到使用Int(s)而不是Long(s))。 scala编译器应该知道如何使用tail-recurse优化这个函数。

10米长是64米。如果你要创建一个64m列表的10m实例,那你就麻烦了。

此外,您可能希望使用延迟流,而不仅仅是尾递归,以便对此函数进行内存优化。 64m本身并不那么大,所以可能不需要这种优化。

我看到它的方式,你要么创建10米的XS参数副本,要么创建ACC副本。

对我来说,xs:参数似乎很懒;但是你要从函数外部保留对它的引用,而stream是scala中的memoizers。

所以我敢打赌那个参数是你得到数百万份的(即scala编译器不知道放弃中间值)。另一个参数应该在每次调用时收集垃圾(或者,因为我们正在讨论尾递归,优化到一个,64m长,在内存列表中)