Laden von NumPy-Arrays von der Festplatte: Vergleich von memmap () und Zarr / HDF5

Wenn Ihr NumPy-Array zu groß ist, um in den Arbeitsspeicher zu passen, können Sie es verarbeiten, indem Sie es in Fragmente aufteilen . Sie können dies entweder im transparenten Modus oder explizit tun, indem Sie diese Fragmente einzeln von der Festplatte laden. In dieser Situation können Sie auf zwei Werkzeugklassen zurückgreifen:





  • NumPy-Methode memmap(), ein transparenter Mechanismus, mit dem Sie eine Datei auf einer Festplatte so wahrnehmen können, als ob sich alles im Speicher befindet. 
  • Zarr- und HDF5-Datenspeicherformate, die einander ähnlich sind und bei Bedarf das Laden von der Festplatte und das Speichern komprimierter Fragmente des Arrays auf der Festplatte ermöglichen.

Jede dieser Methoden hat ihre eigenen Stärken und Schwächen. 

Das Material, dessen Übersetzung wir heute veröffentlichen, widmet sich der Analyse der Merkmale dieser Methoden der Arbeit mit Daten und der Geschichte, in welchen Situationen sie sich als nützlich erweisen können. Besonderes Augenmerk wird auf Datenformate gelegt, die für die Durchführung von Berechnungen optimiert sind und nicht unbedingt dazu bestimmt sind, diese Daten an andere Programmierer zu übertragen.

Was passiert beim Lesen von Daten von einer Festplatte oder beim Schreiben von Daten auf eine Festplatte?


Wenn eine Datei zum ersten Mal von der Festplatte gelesen wird, kopiert das Betriebssystem die Daten nicht nur in den Prozessspeicher. Zunächst kopiert es diese Daten in seinen Speicher und speichert eine Kopie davon im sogenannten „Puffer-Cache“.

Was nützt das hier?

Tatsache ist, dass das Betriebssystem Daten im Cache speichert, falls Sie dieselben Daten erneut aus derselben Datei lesen müssen.


Wenn die Daten erneut gelesen werden, gelangen sie nicht von der Festplatte in den Programmspeicher, sondern aus dem um Größenordnungen schnelleren RAM.


Wenn der vom Cache belegte Speicher für etwas anderes benötigt wird, wird der Cache automatisch geleert.

Wenn Daten auf die Festplatte geschrieben werden, bewegen sie sich in die entgegengesetzte Richtung. Zunächst werden sie nur in den Puffercache geschrieben. Dies bedeutet, dass Schreibvorgänge normalerweise sehr schnell sind, da sich das Programm nicht auf eine langsame Festplatte konzentrieren muss. Sie muss während der Aufnahme nur mit RAM arbeiten.


Infolgedessen werden die Daten aus dem Cache auf die Festplatte geleert.


Arbeiten mit einem Array mit memmap ()


In unserem Fall memmap()können wir eine Datei auf der Festplatte so wahrnehmen, als wäre sie ein im Speicher gespeichertes Array. Das für das Programm transparente Betriebssystem führt Lese- / Schreibvorgänge aus und greift entweder auf den Puffercache oder die Festplatte zu, je nachdem, ob die angeforderten Daten im Speicher zwischengespeichert sind oder nicht. Ein solcher Algorithmus wird hier ausgeführt:

  • Befinden sich die Daten im Cache? Wenn ja - großartig - können Sie sie direkt kontaktieren.
  • Sind die Daten auf der Festplatte? Der Zugriff auf sie ist langsamer, aber Sie müssen sich keine Sorgen machen, sie werden im transparenten Modus geladen.

Als zusätzliches Plus memmap()kann angemerkt werden, dass in den meisten Fällen der Puffercache für die Datei in den Programmspeicher eingebaut wird. Dies bedeutet, dass das System keine zusätzliche Kopie der Daten im Programmspeicher außerhalb des Puffers verwalten muss.


Die Methode memmap()ist in NumPy integriert:

import numpy as np
array = np.memmap("mydata/myarray.arr", mode="r",
                  dtype=np.int16, shape=(1024, 1024))

Wenn Sie diesen Code ausführen, steht Ihnen ein Array zur Verfügung, dessen Arbeit für das Programm vollständig transparent ist - unabhängig davon, ob die Arbeit mit dem Puffercache oder mit der Festplatte ausgeführt wird.

Einschränkungen für Memmap ()


Obwohl es sich in bestimmten Situationen memmap()recht gut zeigen kann, weist diese Methode auch Einschränkungen auf:

  • Daten müssen im Dateisystem gespeichert werden. Daten können nicht aus einem Binärspeicher wie AWS S3 heruntergeladen werden.
  • , . , . , , , .
  • N- , , , . .

Lassen Sie uns den letzten Punkt erklären. Stellen Sie sich vor, wir haben ein zweidimensionales Array mit 32-Bit-Ganzzahlen (4 Byte). Pro Festplatte werden 4096 Bytes gelesen. Wenn Sie Daten, die sich in einer Datei befinden, nacheinander von einer Festplatte lesen (z. B. in Array-Zeilen), haben wir nach jedem Lesevorgang 1024 Ganzzahlen. Wenn Sie jedoch Daten lesen, deren Speicherort in der Datei nicht mit dem Speicherort im Array übereinstimmt (z. B. Daten in Spalten), können Sie bei jedem Lesevorgang nur eine erforderliche Nummer abrufen. Infolgedessen stellt sich heraus, dass Sie tausendmal mehr Lesevorgänge ausführen müssen, um die gleiche Datenmenge zu erhalten.

Zarr und HDF5


Um die oben genannten Einschränkungen zu überwinden, können Sie Zarr- oder HDF5 -Datenspeicherformate verwenden , die sehr ähnlich sind:

  • Sie können mit HDF5-Dateien in Python mit pytables oder h5py arbeiten . Dieses Format ist älter als Zarr und unterliegt weiteren Einschränkungen. Das Plus ist jedoch, dass es in Programmen verwendet werden kann, die in verschiedenen Sprachen geschrieben sind.
  • Zarr ist ein Format, das mit dem gleichnamigen Python-Paket implementiert wurde. Es ist viel moderner und flexibler als HDF5, aber Sie können es (zumindest für den Moment) nur in der Python-Umgebung verwenden. Meiner Meinung nach lohnt es sich in den meisten Situationen, Zarr zu wählen, wenn HDF5 nicht mehrsprachig unterstützt werden muss. Zarr hat zum Beispiel eine bessere Multithreading-Unterstützung.

Weiter werden wir nur Zarr diskutieren, aber wenn Sie sich für das HDF5-Format und seinen tieferen Vergleich mit Zarr interessieren, können Sie dieses Video ansehen .

Mit Zarr


Mit Zarr können Sie Daten speichern und in Form von Arrays in den Speicher laden. Außerdem können Sie diese Daten in Form von Arrays schreiben.

So laden Sie ein Array mit Zarr:

>>> import zarr, numpy as np
>>> z = zarr.open('example.zarr', mode='a',
...               shape=(1024, 1024),
...               chunks=(512,512), dtype=np.int16)
>>> type(z)
<class 'zarr.core.Array'>
>>> type(z[100:200])
<class 'numpy.ndarray'>

Bitte beachten Sie, dass wir bis zum Eingang eines Teils des Objekts nicht zur Verfügung stehen numpy.ndarray. Eine Entität zarr.core.arraybesteht nur aus Metadaten. Es werden nur Daten von der Festplatte geladen, die im Slice enthalten sind.

Warum habe ich Zarr gewählt?


  • Zarr umgeht die memmap()oben diskutierten Einschränkungen :
  • Datenfragmente können auf der Festplatte, im AWS S3-Speicher oder in einem Speichersystem gespeichert werden, das die Möglichkeit bietet, mit Datensätzen im Schlüssel- / Wertformat zu arbeiten.
  • Die Größe und Struktur des Datenfragments wird vom Programmierer bestimmt. Beispielsweise können Daten so organisiert werden, dass Informationen, die sich auf verschiedenen Achsen eines mehrdimensionalen Arrays befinden, effektiv gelesen werden können. Dies gilt für HDF5.
  • Fragmente können komprimiert werden. Gleiches gilt für HDF5.

Lassen Sie uns näher auf die letzten beiden Punkte eingehen.

Fragmentabmessungen


Angenommen, wir arbeiten mit einem Array von 30.000 x 3.000 Elementen. Wenn Sie dieses Array lesen und sich entlang seiner Achse Xund entlang seiner Achse bewegen müssen Y, können Sie Fragmente speichern, die die Daten dieses Arrays enthalten, wie unten gezeigt (in der Praxis benötigen Sie höchstwahrscheinlich mehr als 9 Fragmente):


Jetzt können Daten, die sich sowohl auf der Achse Xals auch auf der Achse Ybefinden, effizient geladen werden. Abhängig davon, welche Art von Daten im Programm benötigt werden, können Sie beispielsweise Fragmente (1, 0), (1, 1), (1, 2) oder Fragmente (0, 1), (1, 1) herunterladen. (2, 1).

Datenkompression


Jedes Fragment kann komprimiert werden. Dies bedeutet, dass Daten schneller in das Programm gelangen können, als auf der Festplatte unkomprimierte Informationen gelesen werden können. Wenn die Daten dreimal komprimiert werden, bedeutet dies, dass sie dreimal schneller von der Festplatte heruntergeladen werden können als nicht komprimierte Daten, abzüglich der Zeit, die der Prozessor zum Entpacken benötigt.


Nachdem die Fragmente heruntergeladen wurden, können sie aus dem Programmspeicher entfernt werden.

Zusammenfassung: memmap () oder Zarr?


memmap()Welches ist besser zu benutzen - oder Zarr?

Memmap()In solchen Fällen sieht es interessant aus:

  • Es gibt viele Prozesse, die Teile derselben Datei lesen. Diese Prozesse können dank der Anwendung memmap()denselben Puffercache gemeinsam nutzen. Dies bedeutet, dass nur eine Kopie der Daten gespeichert werden muss, unabhängig davon, wie viele Prozesse ausgeführt werden.
  • Der Entwickler hat keine Lust, den Speicher manuell zu verwalten. Er plant, sich einfach auf die Funktionen des Betriebssystems zu verlassen, die alle Speicherverwaltungsprobleme automatisch und unsichtbar für den Entwickler lösen.

Zarr ist besonders in folgenden Situationen nützlich (in einigen von ihnen gilt, wie noch erwähnt wird, auch das HDF5-Format):

  • Daten werden von Remote-Quellen heruntergeladen, nicht vom lokalen Dateisystem.
  • Es ist sehr wahrscheinlich, dass der Engpass des Systems von der Festplatte gelesen wird. Die Datenkomprimierung ermöglicht eine effizientere Nutzung der Hardwarefunktionen. Dies gilt auch für HDF5.
  • Wenn Sie mehrdimensionale Arrays entlang verschiedener Achsen erhalten möchten, hilft Zarr bei der Optimierung solcher Operationen, indem die geeignete Größe und Struktur von Fragmenten ausgewählt wird. Dies gilt für HDF5.

Ich würde zwischen memmap()Zarr wählen und zunächst versuchen, Zarr zu verwenden - aufgrund der Flexibilität, die dieses Paket bietet, und des darin enthaltenen Datenspeicherformats.

Liebe Leser! Wie lösen Sie das Problem der Arbeit mit großen NumPy-Arrays?


All Articles