2012-09-20 18 views
6

Ich habe vor kurzem angefangen mit Scala zu spielen und stieß auf folgendes. Im Folgenden sind 4 verschiedene Möglichkeiten, die Zeilen einer Datei zu durchlaufen, einige Dinge zu tun und das Ergebnis in eine andere Datei zu schreiben. Einige dieser Methoden funktionieren so, wie ich es mir vorstelle (obwohl ich dafür eine Menge Speicher verwende), und einige essen Speicher ohne Ende.Scala Iterable Memory Leaks

Die Idee war, Scala getLines Iterator als Iterable zu verpacken. Es ist mir egal, ob es die Datei mehrmals liest - das ist es, was ich von ihm erwarte.

Hier ist mein Repro Code:

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() 

Die Datei, um sie zu lesen ist, ist ~ 10GB mit 1MB Linien.

Die ersten beiden Optionen durchlaufen die Datei mit einer konstanten Speichermenge (~ 100 MB). Das ist, was ich erwarten würde. Der Nachteil ist, dass ein Iterator nur einmal verwendet werden kann und Scala die Call-by-Name-Konvention als Pseudo-Iterable verwendet. (Als Referenz verwendet der äquivalente C# -Code ~ 14 MB)

Die dritte Methode ruft toIterable auf, definiert in TravelableOnce. Das funktioniert, aber es verwendet etwa 2 GB, um die gleiche Arbeit zu tun. Keine Ahnung, wohin der Speicher geht, weil er das gesamte Iterable nicht zwischenspeichern kann.

Die vierte ist am alarmierendsten - sie verwendet sofort den gesamten verfügbaren Speicher und löst eine OOM-Ausnahme aus. Noch seltsamer ist es, dass es dies für alle Operationen tut, die ich getestet habe: Drop, Map und Filter. Wenn man sich die Implementierungen anschaut, scheint keiner von ihnen einen hohen Status beizubehalten (obwohl der Drop etwas verdächtig aussieht - warum zählt er nicht nur die Items?). Wenn ich keine Operationen mache, funktioniert es gut.

Meine Vermutung ist, dass irgendwo Referenzen zu jeder der Zeilen gelesen wird, obwohl ich mir nicht vorstellen kann, wie. Ich habe die gleiche Speicherbelegung gesehen, als ich Iterables in Scala passierte. Wenn ich zum Beispiel Fall 3 (.toIterable) nehme und es an eine Methode übergebe, die einen Iterable [String] in eine Datei schreibt, sehe ich dieselbe Explosion.

Irgendwelche Ideen?

Antwort

6

Hinweis, wie die ScalaDoc of Iterable sagt:

Implementationen dieses Merkmals eine konkrete Methode mit Signatur zur Verfügung stellen müssen:

def iterator: Iterator[A] 

Sie müssen auch newBuilder ein Verfahren bereitzustellen, die einen Builder erstellt für Sammlungen der gleichen Art.

Da Sie nicht eine Implementierung für newBuilder bieten, erhalten Sie die Standardimplementierung, die eine ListBuffer verwendet und damit versucht, alles in den Speicher zu passen.

Sie könnten Iterable.drop als

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

implementieren möchten, aber das würde brechen mit der Darstellung Invarianz der Sammlung Bibliothek (dh iterator.toIterable eine Stream zurückkehrt, während Sie List.drop wollen eine List etc zurück - wodurch die Notwendigkeit für das Builder Konzept).

+1

Interessant ... Ich komme aus C#, wo alles erledigt ist.Aus Neugier - warum sollten sie die gesamte Sequenz als Standardoption puffern? –

+0

Bedeutet dies auch, dass wenn ich eine Sequenz als Iterable [T] -Parameter übergebe, wird sie standardmäßig gepuffert? Wenn dem so ist, besiegt das nicht den Zweck? Ich hatte den Eindruck, dass die Daten nur dann im Speicher gepuffert werden, wenn ich sie explizit über toList, toArray, etc. An frage. –

+0

Ich bin nicht wirklich qualifiziert, das Design der Collection - Bibliothek zu kommentieren (die Standard - Einführung in die Thema ist [hier] (http://www.artima.com/scalazine/articles/scala_collections_architecture.html)). Du hast wirklich nur Probleme, weil du versuchst, Iterable zu verlängern. Mit Stream oder Iterator geht es dir gut. – themel

Verwandte Themen