2012-09-20 17 views
6

最近Scalaでの演奏を開始し、以下を実行しました。以下は、ファイルの行を繰り返し、いくつかのことを行い、別のファイルに結果を書き込む4つの方法です。これらのメソッドのいくつかは、私が思うように(多くのメモリを使用していますが)動作し、いくつかはメモリを無駄に食べます。Scala Iterable Memory Leaks

アイデアは、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() 

それは読んでいるファイルである〜10ギガバイト1MBのラインを持つ:

は、ここに私のREPROのコードです。

最初の2つのオプションは、一定量のメモリ(〜100MB)を使用してファイルを繰り返します。これは私が期待するものです。ここでの欠点は、イテレータは一度しか使用できず、スカラの呼び出し規約を擬似反復可能として使用していることです。 (参考のため、同等のC#コードでは〜14MBを使用します)

3番目のメソッドは、TraverableOnceで定義されたtoIterableを呼び出します。これは動作しますが、同じ作業を行うには約2GBを使用します。 Iterable全体をキャッシュできないため、メモリがどこに向いているのかわかりません。

4番目が最も驚くべきものです。使用可能なすべてのメモリがすぐに使用され、OOM例外がスローされます。私がテストしたすべての操作(ドロップ、マップ、およびフィルタリング)でこれを実行することはさらに熟考しています。実装を見ると、それらのどれも多くの状態を維持しているようには見えません(ドロップが少し疑わしいように見えますが、単に項目を数えるだけではないのはなぜですか?)。私が操作をしなければ、うまく動作します。

私の推測によれば、どのように行を読み上げるかはわかりませんが、私は、IterablesをScalaで渡すときに同じメモリ使用量を見てきました。たとえば、ケース3(.toIterable)をとり、それをIterable [String]をファイルに書き込むメソッドに渡すと、私は同じ爆発を見ます。

アイデア? ScalaDoc of Iterableが言うか

答えて

6

注:彼らはまた、ビルダー を作成する方法newBuilderを提供する必要が

def iterator: Iterator[A] 

:この形質の

実装を 署名で、具体的な方法を提供する必要があります同じ種類のコレクションのために。

あなたはnewBuilderの実装を提供していないので、あなたはListBufferを使用していますので、メモリにすべてをフィットしようとするデフォルトの実装を取得。

あなたはIterable.drop

def drop(n: Int) = iterator.drop(n).toIterable 

ように実装したいかもしれませんが、それはあなたが List.dropListなどを返すようにしたい一方で、 Streamを返し iterator.toIterableコレクションライブラリー(すなわちの表現不変性を破る - これが必要 Builderコンセプトの場合)。

+1

興味深い...私はC#から来ています。好奇心から - なぜ彼らはデフォルトのオプションとして全体のシーケンスをバッファリングするのを選ぶでしょうか? –

+0

これは、シーケンスをIterable [T]パラメータとして渡すと、デフォルトでバッファされることを意味しますか?もしそうなら、その目的を破ることはありませんか?私は、明示的にtoList、toArrayなどを介してデータを要求するときに、データがメモリ内にのみバッファリングされるという印象を受けました。 –

+0

コレクションライブラリの設計についてコメントする資格はありません。トピックは[こちら](http://www.artima.com/scalazine/articles/scala_collections_architecture.html)です)。 Iterableを拡張しようとしているので、実際には問題にぶち当たっています。ストリームまたは反復子でうまくいくでしょう。 – themel