Carga de matrices NumPy desde disco: comparación de memmap () y Zarr / HDF5

Si su matriz NumPy es demasiado grande para caber en la RAM, puede procesarla dividiéndola en fragmentos . Puede hacer esto en modo transparente o explícitamente cargando estos fragmentos del disco uno a la vez. En esta situación, puede recurrir a dos clases de herramientas:





  • Método NumPy memmap(), un mecanismo transparente que le permite percibir un archivo ubicado en un disco como si estuviera todo en la memoria. 
  • Formatos de almacenamiento de datos Zarr y HDF5 que son similares entre sí, que permiten, si es necesario, cargar desde el disco y guardar fragmentos comprimidos de la matriz en el disco.

Cada uno de estos métodos tiene sus propias fortalezas y debilidades. 

El material, cuya traducción publicamos hoy, está dedicado al análisis de las características de estos métodos de trabajo con datos, y la historia de en qué situaciones pueden ser útiles. En particular, se prestará especial atención a los formatos de datos que están optimizados para realizar cálculos y no están necesariamente diseñados para transferir estos datos a otros programadores.

¿Qué sucede al leer datos de un disco o al escribir datos en un disco?


Cuando se lee un archivo del disco por primera vez, el sistema operativo no solo copia los datos en la memoria de proceso. Primero, copia estos datos en su memoria, almacenando una copia de ellos en el llamado "caché de búfer".

¿De qué sirve aquí?

El hecho es que el sistema operativo almacena datos en la memoria caché en caso de que necesite leer los mismos datos del mismo archivo nuevamente.


Si los datos se leen nuevamente, ingresan a la memoria del programa no desde el disco, sino desde la RAM, que es un orden de magnitud más rápido.


Si la memoria ocupada por el caché es necesaria para otra cosa, el caché se borrará automáticamente.

Cuando los datos se escriben en el disco, se mueven en la dirección opuesta. Al principio se escriben solo en la memoria caché del búfer. Esto significa que las operaciones de escritura suelen ser muy rápidas, ya que el programa no necesita enfocarse en un disco lento. Ella, durante la grabación, solo necesita trabajar con RAM.


Como resultado, los datos se vacían al disco desde el caché.


Trabajando con una matriz usando memmap ()


En nuestro caso, memmap()nos permite percibir un archivo en el disco como si fuera una matriz almacenada en la memoria. El sistema operativo, transparente para el programa, realiza operaciones de lectura / escritura, accediendo a la memoria caché del búfer o al disco duro, dependiendo de si los datos solicitados se almacenan en la memoria caché o no. Aquí se ejecuta un algoritmo como este:

  • ¿Están los datos en el caché? Si es así, genial, puede contactarlos directamente.
  • ¿Están los datos en el disco? El acceso a ellos será más lento, pero no tendrá que preocuparse por eso, se cargarán en modo transparente.

Como una ventaja adicional, memmap()se puede observar que en la mayoría de los casos, la memoria caché del búfer para el archivo se integrará en la memoria del programa. Esto significa que el sistema no tiene que mantener una copia adicional de los datos en la memoria del programa fuera del búfer.


El método memmap()está integrado en NumPy:

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

Ejecute este código y tendrá una matriz a su disposición, cuyo trabajo será completamente transparente para el programa, independientemente de si el trabajo se realiza con la memoria caché del búfer o con el disco duro.

Limitaciones de Memmap ()


Aunque en ciertas situaciones memmap()puede mostrarse bastante bien, este método también tiene limitaciones:

  • Los datos deben almacenarse en el sistema de archivos. Los datos no se pueden descargar del almacenamiento binario como AWS S3.
  • , . , . , , , .
  • N- , , , . .

Expliquemos el último punto. Imagine que tenemos una matriz bidimensional que contiene enteros de 32 bits (4 bytes). Se leen 4096 bytes por disco. Si lee datos ubicados en un archivo secuencialmente desde un disco (por ejemplo, dichos datos están en líneas de matriz), luego de cada operación de lectura tendremos 1024 enteros. Pero si lee datos cuya ubicación en el archivo no coincide con su ubicación en la matriz (por ejemplo, datos ubicados en columnas), cada operación de lectura le permitirá obtener solo 1 número requerido. Como resultado, resulta que para obtener la misma cantidad de datos, debe realizar mil veces más operaciones de lectura.

Zarr y HDF5


Para superar las limitaciones anteriores, puede usar los formatos de almacenamiento de datos Zarr o HDF5, que son muy similares:

  • Puede trabajar con archivos HDF5 en Python usando pytables o h5py . Este formato es más antiguo que Zarr y tiene más restricciones, pero su ventaja es que puede usarse en programas escritos en diferentes idiomas.
  • Zarr es un formato implementado usando el paquete Python del mismo nombre. Es mucho más moderno y flexible que HDF5, pero puede usarlo (al menos por ahora) solo en el entorno Python. Según mis sentimientos, en la mayoría de las situaciones, si no hay necesidad de soporte multilingüe para HDF5, vale la pena elegir Zarr. Zarr, por ejemplo, tiene un mejor soporte de subprocesos múltiples.

Además, discutiremos solo sobre Zarr, pero si está interesado en el formato HDF5 y su comparación más profunda con Zarr, puede ver este video.

Usando Zarr


Zarr le permite almacenar datos y cargarlos en la memoria en forma de matrices y, además, escribir estos datos en forma de matrices.

Aquí se explica cómo cargar una matriz con 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'>

Tenga en cuenta que hasta que se reciba una porción del objeto, no estaremos a nuestra disposición numpy.ndarray. Una entidad zarr.core.arrayes solo metadatos. Solo los datos que se incluyen en el segmento se cargan desde el disco.

¿Por qué elegí Zarr?


  • Zarr evita las limitaciones memmap()discutidas anteriormente:
  • Los fragmentos de datos se pueden almacenar en el disco, en el almacenamiento de AWS S3 o en algún sistema de almacenamiento que brinde la capacidad de trabajar con registros de formato de clave / valor.
  • El programador determina el tamaño y la estructura del fragmento de datos. Por ejemplo, los datos se pueden organizar de tal manera que puedan leer de manera eficiente la información ubicada en diferentes ejes de una matriz multidimensional. Esto es cierto para HDF5.
  • Los fragmentos pueden ser comprimidos. Lo mismo puede decirse de HDF5.

Detengámonos en los dos últimos puntos con más detalle.

Dimensiones de fragmentos


Supongamos que estamos trabajando con una matriz de 30,000 x 3,000 elementos de tamaño. Si necesita leer esta matriz y moverse a lo largo de su eje X, y moverse a lo largo de su eje Y, puede guardar fragmentos que contengan los datos de esta matriz, como se muestra a continuación (en la práctica, lo más probable es que necesite más de 9 fragmentos):


Ahora, los datos ubicados tanto en el eje Xcomo en el eje Yse pueden cargar de manera eficiente. Dependiendo de qué tipo de datos se necesitan en el programa, puede descargar, por ejemplo, fragmentos (1, 0), (1, 1), (1, 2) o fragmentos (0, 1), (1, 1), (2, 1).

Compresión de datos


Cada fragmento puede ser comprimido. Esto significa que los datos pueden ingresar al programa más rápido de lo que el disco le permite leer información sin comprimir. Si los datos se comprimen 3 veces, esto significa que se pueden descargar del disco 3 veces más rápido que los datos sin comprimir, menos el tiempo que le toma al procesador desempaquetarlos.


Después de descargar los fragmentos, se pueden eliminar de la memoria del programa.

Resumen: memmap () o Zarr?


memmap()¿Cuál es mejor usar, o Zarr?

Memmap()Parece interesante en tales casos:

  • Hay muchos procesos que leen partes del mismo archivo. Estos procesos, gracias a la aplicación memmap(), podrán compartir la misma caché de búfer. Esto significa que solo una copia de los datos debe mantenerse en la memoria, sin importar cuántos procesos se estén ejecutando.
  • El desarrollador no desea administrar manualmente la memoria. Planea confiar simplemente en las capacidades del sistema operativo, que resolverá todos los problemas de administración de memoria de forma automática e invisible para el desarrollador.

Zarr es especialmente útil en las siguientes situaciones (en algunas de ellas, como se observará, el formato HDF5 también es aplicable):

  • Los datos se descargan de fuentes remotas, no del sistema de archivos local.
  • Es muy probable que el cuello de botella del sistema se lea desde el disco. La compresión de datos permitirá un uso más eficiente de las capacidades de hardware. Esto también se aplica a HDF5.
  • Si necesita obtener segmentos de matrices multidimensionales a lo largo de diferentes ejes, Zarr ayuda a optimizar dichas operaciones seleccionando el tamaño y la estructura apropiados de los fragmentos. Esto es cierto para HDF5.

Elegiría entre memmap()Zarr y, en primer lugar, trataría de usar Zarr, debido a la flexibilidad que brinda este paquete y al formato de almacenamiento de datos que implementa.

¡Queridos lectores! ¿Cómo resuelve el problema de trabajar con grandes matrices NumPy?


All Articles