2013-02-21 9 views
8

In this SO thread, habe ich gelernt, dass ein Verweis auf eine seq auf einer großen Sammlung verhindern wird, dass die gesamte Sammlung von Müll gesammelt wird.Wann sollte ich `seq` in Clojure vermeiden?

Erstens, dieser Thread ist von 2009. Ist dies immer noch in "modernen" Clojure (v1.4.0 oder v1.5.0) wahr?

Zweitens, gilt dieses Problem auch für Lazy-Sequenzen? Zum Beispiel würde (def s (drop 999 (seq (range 1000)))) dem Garbage Collector ermöglichen, die ersten 999 Elemente der Sequenz zu beenden?

Schließlich gibt es eine gute Möglichkeit, dieses Problem für große Sammlungen zu umgehen? Mit anderen Worten, wenn ich einen Vektor von, sagen wir, 10 Millionen Elementen hätte, könnte ich den Vektor so konsumieren, dass die verbrauchten Teile Müll gesammelt werden könnten? Was wäre, wenn ich eine Hashmaps mit 10 Millionen Elementen hätte?

Der Grund, warum ich frage, ist, dass ich auf ziemlich großen Datensätzen arbeite, und ich muss vorsichtiger sein, Verweise auf Objekte nicht zu behalten, so dass die Objekte, die ich nicht brauche, Müll gesammelt werden können. Wie es ist, stoße ich in einigen Fällen auf einen java.lang.OutOfMemoryError: GC overhead limit exceeded Fehler.

+0

Ich denke @Cgrands Beispiel '(fallen 999990 (vec (Bereich 1000000))) ist aufgrund der intervenierenden Vektor und das Verhalten von' subvec'toring. Ich vermute nicht, dass eine faule "cons" Sequenz dies tun würde. Wenn Sie einen Vektor freigeben müssen, während Sie einen Subvektor beibehalten, können Sie den Subvektor in einen neuen Vektor kopieren. Sehr interessante Frage, ich warte darauf, die Antworten zu sehen! –

Antwort

6

Es ist immer der Fall, dass Clojure gezwungen ist, alles im Speicher zu behalten, wenn Sie eine Sequenz "auf dem Kopf halten". Es hat keine Wahl: Sie behalten immer noch einen Hinweis darauf.

Die "GC-Overhead-Grenze erreicht" ist jedoch nicht identisch mit einem Speicherfehler - Es ist wahrscheinlicher, dass Sie eine fiktive Workload ausführen, die Objekte so schnell erstellt und verwirft, dass sie den GC trickst zu denken, dass es überlastet ist.

See:

Wenn Sie eine tatsächliche Arbeitsbelastung auf die Elemente setzen verarbeitet werden, ich vermute, Sie werden sehen, dass dieser Fehler nicht mehr passieren. Sie können in diesem Fall einfach Lazy-Sequenzen verarbeiten, die größer sind als der verfügbare Speicher.

Konkrete Sammlungen wie Vektoren und Hashmaps sind jedoch eine andere Sache: Diese sind nicht faul, müssen also immer komplett im Speicher gehalten werden. Wenn Sie Datensätze größer als Speicher haben dann Ihre Optionen sind:

  • Verwenden faul Sequenzen und halten Sie nicht auf den Kopf
  • Verwenden Spezialsammlungen, die träges Laden unterstützen (Datomic verwendet einige Strukturen wie das glaube ich)
  • Behandeln Sie die Daten als Ereignisstrom (mit etwas wie Storm)
  • Schreiben Sie benutzerdefinierten Code, um die Daten in Blöcke zu partitionieren und sie einzeln zu verarbeiten.
+0

Danke, mikera, das ist hilfreich. Ich freue mich auf Storm und Datomic. –

0

Wenn Sie den Kopf einer Sequenz in einer Bindung halten, dann sind Sie richtig und es kann nicht gc'd sein (und das ist mit jeder Version von Clojure). Wenn Sie eine große Menge an Ergebnissen verarbeiten, warum müssen Sie den Kopf halten?

Um es herum, ja! lazy-seq-Implementierung kann Teile, die bereits "verarbeitet" wurden und nicht direkt innerhalb der Bindung referenziert werden. Stellen Sie nur sicher, dass Sie nicht den Kopf der Sequenz halten.

+1

Ich denke, das Problem ist, dass einige Untersammlungen einen Verweis auf das gesamte Original enthalten, auch wenn es keine Bindung an das Original gibt? Die Dokumentation von 'subvec' zum Beispiel liest" ... der resultierende Vektor teilt die Struktur mit dem Original und es wird kein Trimmen durchgeführt. " Ich nehme an, dass dies die "subvec" Wraps über das Original bedeutet und nur Offsets abruft, aber ich bin nicht sehr weit in das Innere von Clojure gekommen. –

+0

@ A.Webb, ich glaube, dass Sie richtig sind und dass der Untervektor, der aus dem obigen Beispiel erstellt wird, den Kopf der Sammlung behalten wird. (drop 999990 (lazy-seq (Bereich 1000000))) würde aber nicht (zumindest von meinem Verständnis) –

+0

Nichts magisches über das Wrapping einer Sequenz in 'Lazy-Seq' - es gibt nur eine Run-Once-Funktion, die eine zurückgibt 'list *' wrapper um das Gleiche. (Auch "Bereich" ist schon faul.) –

Verwandte Themen