2012-09-20 3 views
6

Recentemente ho iniziato a giocare con Scala e ho trovato quanto segue. Di seguito sono riportati 4 diversi modi per scorrere le righe di un file, eseguire alcune operazioni e scrivere il risultato in un altro file. Alcuni di questi metodi funzionano come penserei (anche se uso molta memoria per farlo) e alcuni consumano memoria a non finire.Scala Iterable Memory Leaks

L'idea era di avvolgere Iterator di Scala su Iterator come Iterable. Non mi interessa se legge il file più volte - questo è quello che mi aspetto che faccia.

Ecco il mio codice 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() 

Il file è la lettura è ~ 10GB con linee 1MB.

Le prime due opzioni eseguono l'iterazione del file utilizzando una quantità costante di memoria (~ 100 MB). Questo è quello che mi aspetterei. Il rovescio della medaglia qui è che un iteratore può essere usato solo una volta e sta usando la convenzione call-by-name di Scala come psuedo-iterable. (Per riferimento, il codice C# equivalente utilizza ~ 14 MB)

Il terzo metodo chiama su Iterable definito in TraverableOnce. Questo funziona, ma utilizza circa 2 GB per fare lo stesso lavoro. Non ho idea di dove stia andando la memoria perché non può memorizzare nella cache l'intero Iterable.

Il quarto è il più allarmante: utilizza immediatamente tutta la memoria disponibile e genera un'eccezione OOM. Ancora più strano è che lo fa per tutte le operazioni che ho testato: rilasciare, mappare e filtrare. Osservando le implementazioni, nessuno di loro sembra mantenere molto stato (anche se la goccia sembra un po 'sospetta - perché non conta solo gli articoli?). Se non eseguo operazioni, funziona correttamente.

La mia ipotesi è che da qualche parte manterrà i riferimenti a ciascuna delle righe lette, anche se non riesco a immaginare come. Ho visto lo stesso uso della memoria quando ho passato Iterables in Scala. Per esempio, se prendo il caso 3 (.toIterable) e lo passo a un metodo che scrive un Iterable [String] in un file, vedo la stessa esplosione.

Qualche idea?

risposta

6

Si noti come il ScalaDoc of Iterable dice:

Le implementazioni di questo tratto necessità di fornire un metodo concreto con firma:

def iterator: Iterator[A] 

Hanno anche bisogno di fornire un metodo newBuilder che crea un costruttore per raccolte dello stesso tipo.

Dal momento che non si forniscono un'implementazione per newBuilder, si ottiene l'implementazione di default, che utilizza un ListBuffer e cerca di rientrare tutto nella memoria così.

Si potrebbe desiderare di implementare Iterable.drop come

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

ma che sarebbe rompere con l'invarianza di rappresentanza della biblioteca collezione (cioè iterator.toIterable restituisce un Stream, mentre si vuole List.drop restituire un List ecc - quindi la necessità per il concetto Builder).

+1

Interessante ... Vengo da C# dove tutto ciò che è curato.Per curiosità: perché sceglierebbero di memorizzare l'intera sequenza come opzione predefinita? –

+0

Significa anche che quando passo una sequenza come parametro Iterable [T] che per default sarà bufferizzata? Se è così, non è questo che sconfigge lo scopo? Avevo l'impressione che i dati sarebbero stati archiviati in memoria solo quando l'avessi chiesto esplicitamente tramite toList, toArray, ecc. –

+0

Non sono davvero qualificato per commentare il progetto della raccolta (l'introduzione standard al topic is [here] (http://www.artima.com/scalazine/articles/scala_collections_architecture.html)). Stai solo incontrando dei problemi perché stai cercando di estendere Iterable, staresti bene con Stream o Iterator. – themel