Scala Iterable Memory Leaks

Mat*_*oek 6 memory-leaks iterable scala

我最近开始玩Scala并遇到以下问题.下面是4种不同的方法来迭代文件的行,做一些事情,并将结果写入另一个文件.其中一些方法可以像我想的那样工作(虽然使用大量内存来执行此操作)并且有些方法会使内存无处不在.

我的想法是将Scala的getLines Iterator包装为Iterable.我不在乎它是否多次读取文件 - 这就是我期望它做的.

这是我的repro代码:

class FileIterable(file: java.io.File) extends Iterable[String] {
  override def iterator = io.Source.fromFile(file).getLines
}

// Iterator

// Option 1: Direct iterator - holds at 100MB
def lines = io.Source.fromFile(file).getLines

// Option 2: Get iterator via method - holds at 100MB
def lines = new FileIterable(file).iterator

// Iterable

// Option 3: TraversableOnce wrapper - holds at 2GB
def lines = io.Source.fromFile(file).getLines.toIterable

// Option 4: Iterable wrapper - leaks like a sieve
def lines = new FileIterable(file)

def values = lines
      .drop(1)
      //.map(l => l.split("\t")).map(l => l.reduceLeft(_ + "|" + _))
      //.filter(l => l.startsWith("*"))

val writer = new java.io.PrintWriter(new File("out.tsv"))
values.foreach(v => writer.println(v))
writer.close()
Run Code Online (Sandbox Code Playgroud)

它正在读取的文件大约10GB,1MB线路.

前两个选项使用恒定的内存量(~100MB)迭代文件.这就是我所期待的.这里的缺点是迭代器只能使用一次,并且它使用Scala的按名称调用约定作为伪迭代.(供参考,等效的c#代码使用~14MB)

第三种方法调用TraverableOnce中定义的Iterable.这个工作,但它使用大约2GB来做同样的工作.不知道内存在哪里,因为它无法缓存整个Iterable.

第四个是最令人担忧的 - 它立即使用所有可用内存并抛出OOM异常.甚至更奇怪的是,它为我测试的所有操作执行此操作:删除,映射和过滤.看看这些实现,它们似乎都没有保持很多状态(虽然下降看起来有点怀疑 - 为什么它不仅仅计算项目?).如果我不做任何操作,它工作正常.

我的猜测是它在某处保持对每行读取的引用,但我无法想象如何.我在Scala中传递Iterables时看到了相同的内存使用情况.例如,如果我采用案例3(.toIterable)并将其传递给将Iterable [String]写入文件的方法,我会看到相同的爆炸.

有任何想法吗?

the*_*mel 6

请注意ScalaDoc的Iterable说法:

这种特性的实现需要提供一个具有签名的具体方法:

  def iterator: Iterator[A]
Run Code Online (Sandbox Code Playgroud)

他们还需要提供一种方法newBuilder,为同类集合创建构建器.

由于您没有提供实现newBuilder,因此您将获得默认实现,该实现使用a ListBuffer并因此尝试将所有内容放入内存中.

您可能希望实现Iterable.drop

def drop(n: Int) = iterator.drop(n).toIterable
Run Code Online (Sandbox Code Playgroud)

但是这会破坏集合库的表示不变性(即iterator.toIterable返回a Stream,而你想List.drop返回一个List等等 - 因此需要这个Builder概念).