2013-02-21 13 views
8

W książce this SO thread dowiedziałem się, że zachowanie odniesienia do zbioru seq w dużej kolekcji uniemożliwi zbieranie całej kolekcji.Kiedy należy unikać używania `seq` w Clojure?

Po pierwsze, ten wątek pochodzi z 2009 r. Czy jest to nadal prawdziwe w "nowoczesnym" Clojure (v1.4.0 lub v1.5.0)?

Po drugie, czy ten problem dotyczy również leniwych sekwencji? Na przykład, czy (def s (drop 999 (seq (range 1000)))) zezwala odśmiecnikowi na wycofanie pierwszych elementów sekwencji? 999?

Wreszcie, czy istnieje dobry sposób obejścia tego problemu w przypadku dużych kolekcji? Innymi słowy, gdybym miał wektor, powiedzmy 10 milionów elementów, mógłbym konsumować wektor w taki sposób, aby zużyte części mogły być zebrane? A co, jeśli miałbym mieszankę z 10 milionami elementów?

Powodem, dla którego pytam, jest to, że operuję na dość dużych zbiorach danych, i muszę być bardziej ostrożny, aby nie przechowywać odniesień do obiektów, tak że obiekty, których nie potrzebuję, mogą być śmieciami zebranymi. W niektórych przypadkach napotykam błąd java.lang.OutOfMemoryError: GC overhead limit exceeded.

+0

Myślę, że przykład @ cgrand '(drop 999990 (vec (range 1000000))) jest spowodowany interweniującym wektorem i zachowaniem' subvec'toring. Nie podejrzewam, że zrobiłaby to leniwy sekwencja "cons". Jeśli chcesz zwolnić wektor zachowując subwektor, możesz skopiować subwektor "do" nowego wektora. Bardzo ciekawe pytanie, czekam też na odpowiedzi! –

Odpowiedz

6

Zawsze jest tak, że jeśli "trzymasz się głowy" sekwencji, Clojure będzie zmuszony zachować wszystko w pamięci. Nie ma wyboru: nadal zachowujesz odniesienie do niego.

Jednak "osiągnięty limit narzutowy GC" nie jest tym samym, co błąd braku pamięci - Jest to bardziej prawdopodobny znak, że uruchamiasz fikcyjne obciążenie, które powoduje tworzenie i odrzucanie obiektów tak szybko, że oszukuje GC do myślenia, że ​​jest przeciążony.

Patrz:

Jeśli umieścisz rzeczywiste obciążenie na pozycji są przetwarzane, podejrzewam, widać, że ten błąd nie będzie się działo więcej. W tym przypadku możesz łatwo przetwarzać leniwe sekwencje, które są większe niż dostępna pamięć.

Konkretne kolekcje, takie jak wektory i hashmaps to jednak inna sprawa: to nie są leniwi, więc musi być zawsze utrzymywane w całości w pamięci. Jeśli masz zestawów danych większych niż pamięć następnie opcje obejmują:

  • Wpisz leniwe sekwencje i nie trzymać głowę
  • Korzystanie specjalistycznych kolekcji, które obsługują leniwy załadunku (Datomic używa niektórych struktur jak to wierzę)
  • Traktuj dane jako strumień zdarzeń (używając czegoś takiego jak Storm)
  • Napisz kod niestandardowy, aby podzielić dane na partycje i przetwarzać je pojedynczo.
+0

Dzięki, mikera, to jest pomocne. Z niecierpliwością czekam na sprawdzenie Storma i Datomica. –

0

Jeśli trzymać głowę w sekwencji wiążącej wtedy są prawidłowe i nie można gc'd (i to z każdej wersji Clojure). Jeśli przetwarzasz dużą liczbę wyników, dlaczego musisz trzymać się głowy?

Jeśli chodzi o sposób obejścia tego, tak! implementacja lazy-seq może zawierać części gc, które zostały już "przetworzone" i nie są bezpośrednio przywoływane z poziomu powiązania. Tylko upewnij się, że nie trzymasz głowy sekwencji.

+1

Myślę, że problem polega na tym, że niektóre podkatalogi zawierają odniesienie do całego oryginału, nawet jeśli nie ma ono wiążącego oryginału? Na przykład dokumentacja 'subvec' brzmi" ... powstaje struktura wektorów z oryginałem i bez przycinania. " Rozumiem przez to, że 'subvec' zawija się nad oryginałem i tylko remanuje przesunięcia, ale nie dotarłem zbyt daleko do wnętrza Clojure. –

+0

@ A.Webb, Wierzę, że masz rację i że wektor podrzędny utworzony na podstawie powyższego przykładu zachowa głowę kolekcji. (kropla 999990 (lazy-seq (zakres 1000000)) nie byłaby jednak (przynajmniej z mojego zrozumienia). –

+0

Nic magicznego na temat owijania sekwencji w 'lazy-seq' - to po prostu daje funkcję run-once zwracającą 'list *' otacza wokół tego samego. (Również "zasięg" jest już leniwy.) –

Powiązane problemy