2012-09-20 17 views
6

Recientemente comencé a jugar con Scala y encontré lo siguiente. A continuación hay 4 maneras diferentes de iterar a través de las líneas de un archivo, hacer algunas cosas y escribir el resultado en otro archivo. Algunos de estos métodos funcionan como yo diría (aunque usan mucha memoria para hacerlo) y algunos consumen memoria sin fin.Scala Iterable Memory Leaks

La idea era ajustar getLines Iterator de Scala como un Iterable. No me importa si lee el archivo varias veces, eso es lo que espero que haga.

Aquí está mi código de 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() 

El archivo que está leyendo es ~ 10 GB con líneas de 1 MB.

Las dos primeras opciones iteran el archivo usando una cantidad constante de memoria (~ 100MB). Esto es lo que esperaría. El inconveniente aquí es que un iterador solo puede usarse una vez y está usando la convención de llamada por nombre de Scala como psuedo-iterable. (Como referencia, el código C# equivalente usa ~ 14MB)

El tercer método llama aIterable definido en TraverableOnce. Éste funciona, pero usa alrededor de 2 GB para hacer el mismo trabajo. No tengo idea de hacia dónde va la memoria porque no puede almacenar en caché todo el Iterable.

El cuarto es el más alarmante: de inmediato utiliza toda la memoria disponible y arroja una excepción OOM. Aún más extraño es que hace esto para todas las operaciones que he probado: drop, map y filter. Al observar las implementaciones, ninguno de ellos parece mantener mucho estado (aunque la caída parece un poco sospechosa, ¿por qué no solo cuenta los artículos?). Si no hago ninguna operación, funciona bien.

Supongo que en algún lugar se mantienen las referencias a cada una de las líneas leídas, aunque no puedo imaginar cómo. He visto el mismo uso de memoria al pasar Iterables en Scala. Por ejemplo, si tomo el caso 3 (.toIterable) y lo transfiero a un método que escribe un [String] Iterable en un archivo, veo la misma explosión.

¿Alguna idea?

Respuesta

6

Nota cómo el ScalaDoc of Iterable dice:

Las implementaciones de este rasgo necesidad de proporcionar un método concreto con firma:

def iterator: Iterator[A] 

También tienen que proporcionar un método newBuilder que crea un constructor para colecciones del mismo tipo.

Dado que usted no proporciona una implementación para newBuilder, se obtiene la implementación por defecto, que utiliza un ListBuffer y por lo tanto trata de encajar todo en la memoria.

Es posible que desee poner en práctica Iterable.drop como

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

pero eso sería romper con la invariancia representación de la biblioteca de colección (es decir iterator.toIterable devuelve un Stream, mientras que usted quiere List.drop para devolver un List etc - por lo tanto la necesidad para el concepto Builder).

+1

Interesante ... Vengo de C# donde todo se soluciona.Por curiosidad, ¿por qué elegirían almacenar en búfer toda la secuencia como la opción predeterminada? –

+0

¿Esto también significa que cuando paso una secuencia como un parámetro Iterable [T] que se almacenará de forma predeterminada? Si es así, ¿eso no derrota el propósito? Tenía la impresión de que los datos solo se almacenarían en la memoria cuando lo solicite explícitamente a través de List, ToArray, etc. –

+0

No estoy realmente calificado para comentar sobre el diseño de la biblioteca de colecciones (la introducción estándar al el tema es [aquí] (http://www.artima.com/scalazine/articles/scala_collections_architecture.html)). En realidad, solo estás teniendo problemas porque estás tratando de extender Iterable, estarás bien con un Stream o Iterator. – themel