2013-02-20 5 views
10

Ich versuche zu verstehen, wie PyTables Daten verwalten, deren Größe größer ist als die Speichergröße. Hier Kommentar in Code von PyTables (link to GitHub):PyTables, die mit Daten arbeiten, deren Größe um ein Vielfaches größer ist als die Größe des Speichers

# Nodes referenced by a variable are kept in `_aliveNodes`. 
# When they are no longer referenced, they move themselves 
# to `_deadNodes`, where they are kept until they are referenced again 
# or they are preempted from it by other unreferenced nodes. 

Auch nützliche Kommentare können innerhalb _getNode Methode gefunden werden.
Es scheint, als ob PyTables ein sehr intelligentes IO-Pufferungssystem haben, das, wie ich es verstehe, vom Benutzer referenzierte Daten im schnellen RAM als "aliveNodes" speichert und referenzierte Daten als "deadNodes" für eine schnelle "Wiederbelebung" bei Bedarf speichert , und liest Daten von der Festplatte, wenn der angeforderte Schlüssel nicht in den Kategorien tot oder lebendig vorhanden ist.

Ich brauche etwas Erfahrung darüber, wie genau PyTables mit Situationen umgehen, wenn mit Daten gearbeitet wird, die größer sind als der verfügbare Speicher. Meine spezifischen Fragen:

  1. Wie funktioniert deadNode/aliveNode System (allgemeines Bild)?
  2. Was ist der Hauptunterschied zwischen aliveNodes/deadNodes, während sie beide im RAM gespeicherten Daten darstellen, wenn im richtigen?
  3. Kann die RAM-Grenze für die Pufferung manuell angepasst werden? Unterhalb des Kommentars befindet sich Code, der einen Wert von params['NODE_CACHE_SLOTS'] liest. Kann es irgendwie vom Benutzer angegeben werden? Zum Beispiel, wenn ich etwas RAM für andere Anwendungen, die auch Speicher benötigen, lassen möchte?
  4. In welchen Situationen kann PyTables abstürzen oder erheblich verlangsamen, wenn mit großen Datenmengen von Daten gearbeitet wird? Kann in meinem Fall die Erinnerung um das 100-fache überschreiten, was sind häufige Fallstricke in solchen Situationen?
  5. Welchen Nutzen von PyTables in Bezug auf Größe, Struktur der Daten und Manipulationen mit Daten, die als "richtig" für das Erreichen der besten Leistung angesehen werden?
  6. Docs suggests Verwenden Sie .flush() nach jedem grundlegenden .append() Zyklus. Wie lange kann dieser Zyklus tatsächlich sein? Ich führe einen kleinen Benchmark durch und vergleiche SQLite und PyTables, wie sie mit der Erstellung einer riesigen Tabelle mit Schlüssel/Wert-Paaren aus großen CSV-Dateien umgehen können. Und wenn ich .flush() verwende, weniger häufig im Hauptzyklus, erhält PyTables enorme Beschleunigung. Also - ist es richtig, zu .append() relativ großen Datenstücken, und dann .flush() verwenden?
+2

Sie können keinen Inhalt im Speicher speichern, der 100x Ihres verfügbaren Arbeitsspeichers ist. PyTables kann Ihnen jedoch helfen, auf die Daten in Chunks zuzugreifen oder Funktionen (manchmal) speichereffizient auf Ihre Daten anzuwenden. Was versuchen Sie mit Ihren Daten? – seandavi

Antwort

2

Speicherstruktur

Nie verwendet pytables aber auf den Quellcode suchen:

class _Deadnodes(lrucacheExtension.NodeCache): 
    pass 

So sieht es aus wie die _deadnodes einen LRU-Cache implementiert sind. LRU == "Least Recently Used" bedeutet, dass zuerst der am wenigsten verwendete Knoten weggeworfen wird. Die Quelle ist here.

Welche sie als ein benutzerdefiniertes Wörterbuch von Knoten verwenden, die tatsächlich im Programm ausgeführt werden und dargestellt werden.

sehr vereinfachtes Beispiel (Knoten sind Buchstaben, Zahlen im Cache angeben, wie abgestanden ein Eintrag):

memory of 4, takes 1 time step 
cache with size 2, takes 5 times steps 
disk with much much more, takes 50 time steps 

get node A //memory,cache miss load from disk t=50 
get node B // "" t=100 
get node C // "" t=150 
get node D // "" t=200 
get node E // "" t=250 
get node A //cache hit load from cache t=255 
get node F //memory, cache miss load from disk t=305 
get node G //memory, cache miss load from disk t=355 
get node E // in memory t=356 (everything stays the same) 

t=200    t=250    t=255 
Memory CACHE Memory CACHE Memory CACHE 
A     E   A0  E   B0 
B     B     A 
C     C     C 
D     D     D 

t=305    t=355    
Memory CACHE Memory CACHE 
E   B1  E   G0 
A   C0  A   C1 
F     F 
D     G 

Wie Sie im wirklichen Leben kennen diese Strukturen sind sehr groß und die Zeit, die sie für den Zugriff nimmt, ist in Buszyklen, also 1/(Uhr deines PCs).

Die Zeit für den Zugriff auf die Elemente ist vergleichbar. Es ist ziemlich vernachlässigbar für den Speicher, ein wenig mehr für den Cache und eine Menge mehr für die Festplatte. Das Lesen von der Platte ist der längste Teil des gesamten Prozesses. die Scheibe und der Arm müssen sich bewegen usw. Es ist ein physikalischer Prozess und kein elektronischer Prozess, da er nicht mit Lichtgeschwindigkeit stattfindet.

Hier in Pytables tun sie etwas ähnliches. Sie haben ihren eigenen Cache-Algorithmus in Cython geschrieben, der ein Mittler zwischen den lebendigen Knoten (Speicher) und den vollständigen Daten (Festplatte) ist. Wenn die Trefferquote zu niedrig ist, sieht es so aus, als ob der Cache ausgeschaltet wird und nach einer bestimmten Anzahl von Zyklen wieder eingeschaltet wird.

In parameters.py die DISABLE_EVERY_CYCLE, ENABLE EVERY_CYCLE und LOWEST_HIT_RATIO Variablen um die Anzahl der Zyklen unter LOWEST_HIT_RATIO und zu deaktivieren, nachdem die Anzahl von Zyklen zu definieren, werden verwendet, um zu warten, wieder zu aktivieren. Es wird abgeraten, diese Werte zu ändern.

Die wichtigste Sache, die Sie daraus ziehen sollten, ist, dass, wenn Sie für ein großes Dataset verarbeiten müssen, stellen Sie sicher, dass sie auf den gleichen Knoten sind. Wenn du damit durchkommst, lies ein Stück, bearbeite das Chuck, erhalte deine Ergebnisse und lade dann einen weiteren Chunk. Wenn Sie Chunk A laden, erhalten Sie einen anderen Chunk B, und laden Sie Chunk A erneut. Dies verursacht die größte Verzögerung. Arbeiten Sie immer nur an einem Stück Daten gleichzeitig und behalten Sie Zugriff und Schreibvorgänge auf ein Minimum. Sobald ein Wert in _alivenodes ist, ist es schnell zu ändern, _deadnodes ist ein bisschen langsamer, und keiner ist viel langsamer.

NODE_CACHE_SLOTS

params['NODE_CACHE_SLOTS'] die Größe des Satzes von toten Knoten definiert. Wenn Sie ihn auf parameters.py zurücksetzen, wird er standardmäßig auf 64 gesetzt. Er gibt an, dass Sie verschiedene Werte ausprobieren und einen Bericht erstellen können. Sie könnten entweder den Wert in der Datei ändern oder Folgendes tun:

import parameters 
parameters.NODE_CACHE_SLOTS = # something else 

Dies begrenzt nur die Anzahl der Knoten im Cache. Vergangenheit, dass Sie von Pythons Heap-Größe begrenzt sind, um das zu setzen, siehe this.

append/flush

Für append, versichert flush die Zeilen werden an den Tisch.Je mehr Daten Sie damit verschieben, desto länger dauert es, bis sich die Daten vom internen Puffer zur Datenstruktur bewegen. Es ruft eine geänderte Version der H5TBwrite_records-Funktion mit anderem Bearbeitungscode auf. Ich nehme an, die Länge des Anrufs bestimmt, wie lang der Ausgabezyklus ist.

Denken Sie daran, dies ist alles aus dem Quellcode, und keine zusätzliche Magie, die sie versuchen, zu tun. Ich habe nie Pytables verwendet. Theoretisch sollte es nicht zum Absturz kommen, aber wir leben nicht in einer theoretischen Welt.

Edit:

Eigentlich ein Bedürfnis nach pytables finde ich mich über this question in ihrer FAQ gekommen, dass einige Ihrer Bedenken beantworten könnte.

Vielen Dank für die Offenlegung Pytables mir, wenn ich .h5 Dateien vor der Untersuchung dieser Frage gestoßen wäre hätte ich nicht gewusst, was zu tun ist.

1

Ich bin kein Experte in PyTable aber die meisten wahrscheinlich funktioniert wie swap memory.

Die aliveNodes leben im RAM, während die deadNodes sind wahrscheinlich auf der Festplatte in hdf5-Dateien gespeichert (das binäre Dateiformat von PyTables verwendet). Jedes Mal, wenn Sie auf ein Stück Daten zugreifen müssen, muss es im RAM sein. Daher überprüft PyTable, ob es bereits vorhanden ist (aliveNodes) und gibt es an Sie zurück, wenn dies der Fall ist. Andernfalls muss die deadNode wiederbeleben, wo die Daten leben. Da der RAM begrenzt ist, wird es wahrscheinlich töten (auf Platte schreiben) eine unbenutzte aliveNode, um etwas Platz im Voraus zu machen.

Der Grund für diesen Prozess ist natürlich die begrenzte Größe des RAM. Die Konsequenz ist, dass die Performances jedes Mal betroffen sind, wenn Sie einen Knoten tauschen müssen (Kill ein Knoten und wiederbeleben ein anderes).

Um die Leistung zu optimieren, sollten Sie versuchen, den Austausch zu minimieren. Wenn Ihre Daten beispielsweise parallel verarbeitet werden können, können Sie möglicherweise jeden Knoten nur einmal laden. Anderes Beispiel: Stellen Sie sich vor, dass Sie jedes Element einer riesigen Matrix, die in ein Gitter von Knoten aufgeteilt ist, durchlaufen müssen. Dann sollten Sie besser vermeiden, auf seine Elemente nach Zeile oder Spalte zuzugreifen, sondern Knoten für Knoten.

Natürlich behandelt PyTable dies unter der Haube, so dass Sie nicht die Kontrolle über das haben, was in jedem Knoten ist (aber ich ermutige Sie, um diese NODE_CACHE_SLOTS Variable zu graben, zumindest zu verstehen, wie es funktioniert). Im Allgemeinen ist es jedoch schneller, auf Daten zuzugreifen, die zusammenhängend sind und nicht überall verstreut sind. Wie immer, wenn die Zeitperformance ein wichtiges Problem für Ihre Anwendung (en) ist, profilieren Sie Ihren Code.


Übersetzung: Ich weiß kaum etwas über PyTables

0

Ich bin auch kein Experte in PyTable und Simon scheint gut das Konzept der Swap-Speicher abgedeckt zu haben, aber Wenn Sie ein konkretes Beispiel für einen Algorithmus haben möchten, der dafür ausgelegt ist, mit Daten umzugehen, die zu groß sind, um in den Speicher zu passen, würde ich empfehlen, auf externe Sortierung zu achten.

Die Grundidee ist folgende: Sie können nicht alle Ihre Daten in den Speicher passen, aber Sie müssen es sortieren. Sie können jedoch einige der Daten im Speicher, in Blöcken der Größe k passen. Sagen wir, es gibt j solche Blöcke.

  • Teilen Sie die Daten in Blöcke der Größe k.
  • Für jeden Block, bringen Sie es in den Speicher und sortieren Sie es (z. B. mit Quicksort oder was auch immer) und schreiben Sie dann seine sortierte Version zurück auf die Festplatte.

Jetzt haben wir j Blöcke von sortierten Daten, die wir in eine lange sortierte Stück von Daten zu zusammenführen möchten. Dieses Problem klingt wie Mergesort! So

  • Bringen Sie den niedrigsten Wert aus jedem der j-Blöcke in den Speicher sortiert
  • Finden Sie die kleinste dieser j-Werte. Das ist das kleinste Stück Daten! Schreiben Sie das also als Startpunkt für den sortierten Datensatz auf die Festplatte.
  • Ersetzen Sie den neu geschriebenen Wert mit dem nächstkleinsten Wert von seinem Block in den Speicher (dies ist das 'swapping' Bit des Swap-Speichers).

Nun werden die Daten im Speicher sind die kleinste j, außer für die, die wir schon schrieben in die letzten auf der Platte festgelegt sortierten Daten. Wenn wir diesen Prozess wiederholen, bis alle Daten in den endgültigen Satz geschrieben sind, wird er immer sortiert.

Also, das ist nur ein Beispiel für einen Algorithmus, der Memory-Swapping verwendet, um Daten zu verarbeiten, die zu groß sind, um in den Speicher zu passen. Die Sortiermethoden von PyTable sind wahrscheinlich in diese Richtung.

Bonus: Here sind some Links to mehr Erklärungen externer Art.