Scala 可迭代内存泄漏
Posted
技术标签:
【中文标题】Scala 可迭代内存泄漏【英文标题】:Scala Iterable Memory Leaks 【发布时间】:2012-09-12 08:44:45 【问题描述】:我最近开始使用 Scala 并遇到以下问题。下面是 4 种不同的方法来遍历文件的行,做一些事情,并将结果写入另一个文件。其中一些方法如我所想(尽管使用大量内存来做到这一点),而另一些则无休止地消耗内存。
这个想法是将 Scala 的 getLines Iterator 包装为一个 Iterable。我不在乎它是否会多次读取文件——这就是我期望它做的事情。
这是我的复制代码:
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()
它正在读取的文件约为 10GB,其中 1MB 行。
前两个选项使用恒定的内存量 (~100MB) 迭代文件。这是我所期望的。这里的缺点是迭代器只能使用一次,并且它使用 Scala 的按名称调用约定作为伪可迭代。 (作为参考,等效的 c# 代码使用 ~14MB)
第三个方法调用定义在 TraverableOnce 中的 toIterable。这个工作,但它使用大约 2GB 来做同样的工作。不知道内存的去向,因为它无法缓存整个 Iterable。
第四个是最令人震惊的——它立即使用所有可用内存并抛出 OOM 异常。更奇怪的是,它对我测试过的所有操作都执行此操作:drop、map 和 filter。查看实现,它们似乎都没有保持太多状态(尽管下降看起来有点可疑 - 为什么它不只计算项目?)。如果我不做任何操作,它就可以正常工作。
我的猜测是它在某处维护对所读取的每一行的引用,尽管我无法想象如何。在 Scala 中传递 Iterables 时,我看到了相同的内存使用情况。例如,如果我采用案例 3 (.toIterable) 并将其传递给将 Iterable[String] 写入文件的方法,我会看到同样的爆炸。
有什么想法吗?
【问题讨论】:
【参考方案1】:注意ScalaDoc of Iterable
的说法:
这个 trait 的实现需要提供一个具体的方法 签名:
def iterator: Iterator[A]
他们还需要提供一个方法
newBuilder
来创建一个构建器 用于同类收藏。
由于您没有为 newBuilder
提供实现,因此您将获得默认实现,它使用 ListBuffer
并因此尝试将所有内容放入内存中。
您可能希望将Iterable.drop
实现为
def drop(n: Int) = iterator.drop(n).toIterable
但这会破坏集合库的表示不变性(即iterator.toIterable
返回Stream
,而您希望List.drop
返回List
等 - 因此需要Builder
概念) .
【讨论】:
有趣...我来自 c#,所有这些都得到了照顾。出于好奇——他们为什么会选择缓冲整个序列作为默认选项? 这是否也意味着当我将序列作为 Iterable[T] 参数传递时,默认情况下它会被缓冲?如果是这样,那不是违背了目的吗?我的印象是,当我通过 toList、toArray 等明确要求数据时,数据只会被缓冲在内存中。 收藏库的设计我真的没有资格评论(主题的标准介绍是here)。您实际上只是遇到问题,因为您正在尝试扩展 Iterable,您可以使用 Stream 或 Iterator。以上是关于Scala 可迭代内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章
无法隔离 Scala 批量数据加载应用程序中的 JDBC 内存泄漏