2012-09-20 3 views
6

J'ai récemment commencé à jouer avec Scala et j'ai traversé les étapes suivantes. Vous trouverez ci-dessous 4 façons différentes de parcourir les lignes d'un fichier, de faire des choses et d'écrire le résultat dans un autre fichier. Certaines de ces méthodes fonctionnent comme je le pense (mais en utilisant beaucoup de mémoire pour le faire) et certains mangent de la mémoire sans fin.Scala Iterable Memory fuites

L'idée était d'envelopper Iterator de Scala dans getLines Iterable. Je me fiche de lire le fichier plusieurs fois - c'est ce que je m'attends à faire.

Voici mon code 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() 

Le fichier, il est la lecture est ~ 10GB avec des lignes 1MB.

Les deux premières options parcourent le fichier en utilisant une quantité constante de mémoire (~ 100 Mo). C'est ce à quoi je m'attendrais. L'inconvénient est qu'un itérateur ne peut être utilisé qu'une seule fois et qu'il utilise la convention call-by-name de Scala comme pseudo-exécutable. (Pour référence, le code C# équivalent utilise ~ 14 Mo)

La troisième méthode appelle toIterable définie dans TraverableOnce. Celui-ci fonctionne, mais il utilise environ 2 Go pour faire le même travail. Aucune idée de l'emplacement de la mémoire, car elle ne peut pas mettre en cache l'ensemble de l'Iterable. Le quatrième est le plus alarmant - il utilise immédiatement toute la mémoire disponible et déclenche une exception de MOO. Encore plus bizarre, c'est qu'il le fait pour toutes les opérations que j'ai testées: drop, map et filter. En regardant les implémentations, aucune d'entre elles ne semble maintenir beaucoup d'état (bien que la goutte semble un peu suspecte - pourquoi ne compte-t-elle pas simplement les objets?). Si je ne fais aucune opération, ça fonctionne bien. Je pense que quelque part il maintient des références à chacune des lignes lues, mais je ne peux pas imaginer comment. J'ai vu la même utilisation de la mémoire lors du passage d'Iterables à Scala. Par exemple, si je prends le cas 3 (.toIterable) et le passe à une méthode qui écrit une Iterable [String] dans un fichier, je vois la même explosion.

Des idées?

Répondre

6

Notez comment le ScalaDoc of Iterable dit:

Implémentations de ce trait ont besoin d'une méthode concrète avec signature:

def iterator: Iterator[A] 

Ils doivent également fournir une méthode newBuilder qui crée un constructeur pour des collections du même genre.

Puisque vous ne fournissez pas une implémentation pour newBuilder, vous obtenez l'implémentation par défaut, qui utilise un ListBuffer et essaie ainsi d'adapter tout en mémoire.

Vous pouvez mettre en œuvre Iterable.drop comme

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

mais ce serait rompre avec l'invariance de représentation de la bibliothèque de collection (c.-à-iterator.toIterable retourne un Stream, alors que vous voulez List.drop retourner un List etc - ainsi la nécessité pour le concept Builder).

+1

Intéressant ... Je viens de C# où tout est pris en charge.Par curiosité - pourquoi choisiraient-ils de tamponner la séquence entière comme option par défaut? –

+0

Cela signifie-t-il également que lorsque je transmets une séquence en tant que paramètre Iterable [T], elle sera par défaut tamponnée? Si oui, cela ne va-t-il pas à l'encontre du but? J'avais l'impression que les données ne seraient tamponnées en mémoire que lorsque je les demanderais explicitement via toList, toArray, etc. –

+0

Je ne suis pas vraiment qualifié pour commenter la conception de la bibliothèque de collection (l'introduction standard à la le sujet est [ici] (http://www.artima.com/scalazine/articles/scala_collections_architecture.html)). Vous ne faites vraiment que rencontrer des problèmes parce que vous essayez d'étendre Iterable, vous seriez bien avec un Stream ou un Iterator. – themel