2012-09-20 16 views
6

Niedawno zacząłem grać ze Scalą i natknąłem się na następujące rzeczy. Poniżej znajdują się 4 różne sposoby na przeglądanie linii pliku, robienie pewnych rzeczy i zapisywanie wyniku do innego pliku. Niektóre z tych metod działają tak, jak bym pomyślał (choć używa się do tego dużej ilości pamięci), a niektóre jedzą pamięć bez końca.Scala Iterable Memory Leaks

Pomysł polegał na owinięciu Iterable Scala's getLines Iterator. Nie obchodzi mnie, czy czyta plik kilka razy - właśnie tego oczekuję.

Oto mój kod 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() 

plik to czytanie jest ~ 10GB z liniami 1 MB.

Pierwsze dwie opcje iterują plik przy użyciu stałej ilości pamięci (~ 100MB). Tego mogłem się spodziewać. Wadą tego jest to, że iterator może być użyty tylko raz i używa konwencji Call-by-Name Scala jako pseudo-iterable. (Dla odniesienia, równoważny kod C# wykorzystuje ~ 14MB)

Trzeci sposób wywołania toIterable zdefiniowany w TraverableOnce. Ten działa, ale wykorzystuje około 2 GB do wykonania tej samej pracy. Nie mam pojęcia, dokąd zmierza pamięć, ponieważ nie może buforować całego Iterable.

Czwarty jest najbardziej alarmujący - natychmiast wykorzystuje całą dostępną pamięć i generuje wyjątek OOM. Jeszcze dziwniejsze jest to, że robi to dla wszystkich operacji, które testowałem: upuszczanie, mapowanie i filtrowanie. Patrząc na implementacje, żadna z nich nie wydaje się utrzymywać dużego stanu (choć spadek wygląda na podejrzanego - dlaczego nie liczy się tylko z przedmiotami?). Jeśli nie wykonam żadnych operacji, to działa dobrze.

Moje przypuszczenie jest takie, że gdzieś zachowuje odniesienia do każdej z przeczytanych linii, chociaż nie potrafię sobie wyobrazić, w jaki sposób. Widziałem to samo użycie pamięci podczas przekazywania Iterables w Scala. Na przykład jeśli wezmę sprawę 3 (.toIterable) i przekażę ją do metody, która zapisuje Iterable [String] do pliku, widzę ten sam wybuch.

Wszelkie pomysły?

Odpowiedz

6

Uwaga jak ScalaDoc of Iterable mówi:

Implementacje tej cechy konieczne jest dostarczenie sposobu betonie Podpis:

def iterator: Iterator[A] 

Muszą także dostarczenie sposobu newBuilder która tworzy budowniczy dla kolekcji tego samego rodzaju.

Ponieważ nie stanowią implementację newBuilder, masz domyślną implementację, która używa ListBuffer i dlatego stara się dopasować wszystko do pamięci.

Możesz chcieć realizować Iterable.drop jak

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

jednak, że zerwanie z niezmienniczości reprezentacji bibliotece zbierania (tj iterator.toIterable zwraca Stream, a chcesz List.drop do zwróci List itp - stąd konieczność dla koncepcji Builder).

+1

Interesujące ... Pochodzę z C#, gdzie wszystko jest załatwione.Z ciekawości - dlaczego mieliby buforować całą sekwencję jako domyślną opcję? –

+0

Czy to oznacza również, że kiedy przekazuję sekwencję jako parametr Iterable [T], który będzie domyślnie buforowany? Jeśli tak, to czy to nie jest celem? Odniosłem wrażenie, że dane będą buforowane tylko w pamięci, gdy jednoznacznie o to poprosię poprzez toList, toArray, itd. –

+0

Nie jestem naprawdę kwalifikowany do komentowania projektu biblioteki kolekcji (standardowe wprowadzenie do tematem jest [tutaj] (http://www.artima.com/scalazine/articles/scala_collections_architecture.html)). Naprawdę masz problemy tylko dlatego, że próbujesz rozszerzyć Iterable, nie będziesz miał problemu z Streamem lub Iteratorem. – themel