我写了一个函数,给定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
?我该如何预防?
答案 0 :(得分:0)
您的功能存在一些问题。
您的go
函数具有反转输出流中元素的效果,因为您将附加到累加器的末尾。
此外,附加行为而不是前置行为会导致整个累加器具体化为List,然后在迭代的每一步复制。这就是造成OutOfMemoryError的原因,因为你的实现需要二次内存。
此外,由于您未处理match
的情况,因此a == 4000000L
阻止并非详尽无遗。
如果您只想过滤掉大于4000000的元素,这就足够了:
xs filterNot {_ > 4000000L}
答案 1 :(得分:0)
嗯,已经有一个filter
方法用于流,但我想你想自己做。
Stream.range(0,10000000L)
只会在内存中有0
,因为其余内容会被懒惰地评估。
但是在您的filterLt4Mil
方法中,您遍历整个流,导致评估此流中的所有元素,从而导致从0
到10000000L
的所有数字都存储在内存中...
存储此类金额可能会导致OutOfMemoryException
。
例如:x.forall(_ >= 0)
也会产生OutOfMemoryException
,forall
也必须在_ >= 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长,在内存列表中)