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?
Interessante ... Vengo da C# dove tutto ciò che è curato.Per curiosità: perché sceglierebbero di memorizzare l'intera sequenza come opzione predefinita? –
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. –
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