为什么Stream.filter内存不足?

时间:2014-04-09 15:20:12

标签: scala scala-collections

这两个表达式应该是一样的:

Stream.from(1).filter(_ < 0).head
Stream.from(1).find(_ < 0)

应该循环,直到它们返回Int.MinValue。这正是filter的版本所做的,但find生成了OutOfMemoryError。不过看一下它们的实现,我无法弄清楚这两个版本都不会产生OutOfMemoryError

以下是Stream.filter的实施:

override def filter(p: A => Boolean): Stream[A] = {
  // optimization: drop leading prefix of elems for which f returns false
  // var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise
  var rest = this
  while (!rest.isEmpty && !p(rest.head)) rest = rest.tail
  // private utility func to avoid `this` on stack (would be needed for the lazy arg)
  if (rest.nonEmpty) Stream.filteredTail(rest, p)
  else Stream.Empty
}

find继承自LinearSeqOptimized,其定义为:

override /*IterableLike*/
def find(p: A => Boolean): Option[A] = {
  var these = this
  while (!these.isEmpty) {
    if (p(these.head)) return Some(these.head)
    these = these.tail
  }
  None
}

它们都有一个while循环,它丢弃不满足谓词的Stream元素。因为this应该保持对Stream开头的引用,所有这些创建的元素应该在内存中累积,直到我们用完空间。除非我真的误解了这里发生的事情,Stream.filter在进入while循环之前以某种方式从堆栈帧中消除thisStream.filter中关于未使用dropWhile原因的评论看起来像是一个暗示,但我不知道它是指什么。

我的下一步是学习如何反汇编和读取JVM字节码,但我真的希望有人知道这里发生了什么。

1 个答案:

答案 0 :(得分:3)

它是HotSpot和Scala的特征实现方式的结合。

如果我使用-Xint关闭HotSpot,Stream.filter也会因OutOfMemoryException而死亡。在生成的字节码本身中,this和变量restthese存储在不同的内存位置,但由于this仅用于初始化这些变量,我相信HotSpot是足够聪明,可以简单地重用this的内存位置。这解释了为什么Stream.filter不会耗尽内存。

HotSpot对Stream.filter的优化也应适用于LinearSeqOptimized.find,但由于实施了特征的方式,因此会保留对this的引用。当在特征内部实现方法时,Scala将该方法编译为静态方法。当一个类继承该特征时,Scala会创建一个调用静态方法的小型存根方法。因此,即使HotSpot优化LinearSeqOptimized.find的静态方法,存根方法的堆栈帧仍然引用this