Estoy procesando matrices 3D grandes, que a menudo necesito cortar de varias maneras para realizar una variedad de análisis de datos. Un "cubo" típico puede tener ~ 100 GB (y probablemente aumentará de tamaño en el futuro)
Parece que el formato de archivo recomendado típico para grandes conjuntos de datos en python es usar HDF5 (ya sea h5py o pytables). Mi pregunta es: ¿hay algún beneficio de velocidad o uso de memoria al usar HDF5 para almacenar y analizar estos cubos en lugar de almacenarlos en archivos binarios planos simples? ¿Es HDF5 más apropiado para datos tabulares, en comparación con arreglos grandes como los que estoy trabajando? Veo que HDF5 puede proporcionar una buena compresión, pero estoy más interesado en la velocidad de procesamiento y lidiar con el desbordamiento de la memoria.
Con frecuencia quiero analizar solo un subconjunto grande del cubo. Un inconveniente de pytables y h5py es que parece que cuando tomo una porción de la matriz, siempre obtengo una matriz numpy, usando memoria. Sin embargo, si corto un mapa de memoria numeroso de un archivo binario plano, puedo obtener una vista, que mantiene los datos en el disco. Entonces, parece que puedo analizar más fácilmente sectores específicos de mis datos sin sobrecargar mi memoria.
He explorado tanto pytables como h5py, y hasta ahora no he visto el beneficio de ninguno para mi propósito.
h5py
se adapta mejor a conjuntos de datos como el suyo quepytables
. Además,h5py
no no devolver una matriz numpy en memoria. En su lugar, devuelve algo que se comporta como uno, pero que no se carga en la memoria (similar a unamemmapped
matriz). Estoy escribiendo una respuesta más completa (puede que no la termine), pero espero que este comentario ayude un poco mientras tanto.type(cube)
dah5py._hl.dataset.Dataset
. Mientrastype(cube[0:1,:,:])
danumpy.ndarray
.Respuestas:
Ventajas de HDF5: organización, flexibilidad, interoperabilidad
Algunas de las principales ventajas de HDF5 son su estructura jerárquica (similar a carpetas / archivos), metadatos arbitrarios opcionales almacenados con cada elemento y su flexibilidad (por ejemplo, compresión). Esta estructura organizativa y el almacenamiento de metadatos pueden parecer triviales, pero son muy útiles en la práctica.
Otra ventaja de HDF es que los conjuntos de datos pueden ser de tamaño fijo o flexible. Por lo tanto, es fácil agregar datos a un gran conjunto de datos sin tener que crear una copia completamente nueva.
Además, HDF5 es un formato estandarizado con bibliotecas disponibles para casi cualquier idioma, por lo que compartir sus datos en disco entre, por ejemplo, Matlab, Fortran, R, C y Python es muy fácil con HDF. (Para ser justos, tampoco es demasiado difícil con una gran matriz binaria, siempre que conozca el orden C vs.F y conozca la forma, el tipo d, etc. de la matriz almacenada).
Ventajas de HDF para un arreglo grande: E / S más rápida de un segmento arbitrario
Al igual que el TL / DR: para una matriz 3D de ~ 8GB, leer un corte "completo" a lo largo de cualquier eje tomó ~ 20 segundos con un conjunto de datos HDF5 fragmentado, y 0.3 segundos (el mejor de los casos) a más de tres horas (el peor de los casos) para una matriz en mapa de memoria de los mismos datos.
Más allá de las cosas enumeradas anteriormente, existe otra gran ventaja para un formato de datos en disco "fragmentado" * como HDF5: leer un segmento arbitrario (énfasis en arbitrario) generalmente será mucho más rápido, ya que los datos en disco son más contiguos en promedio.
*
(HDF5 no tiene que ser un formato de datos fragmentado. Admite fragmentación, pero no lo requiere. De hecho, el valor predeterminado para crear un conjunto de datos enh5py
no es fragmentar, si no recuerdo mal).Básicamente, la velocidad de lectura del disco en el mejor de los casos y la velocidad de lectura del disco en el peor de los casos para una porción determinada de su conjunto de datos será bastante similar con un conjunto de datos HDF fragmentado (suponiendo que elija un tamaño de porción razonable o deje que una biblioteca elija uno por usted). Con una matriz binaria simple, el mejor de los casos es más rápido, pero el peor de los casos es mucho peor.
Una advertencia, si tiene un SSD, es probable que no note una gran diferencia en la velocidad de lectura / escritura. Sin embargo, con un disco duro normal, las lecturas secuenciales son mucho, mucho más rápidas que las lecturas aleatorias. (es decir, un disco duro normal tiene mucho
seek
tiempo). HDF todavía tiene una ventaja sobre un SSD, pero se debe más a sus otras características (por ejemplo, metadatos, organización, etc.) que a la velocidad bruta.En primer lugar, para aclarar la confusión, acceder a un
h5py
conjunto de datos devuelve un objeto que se comporta de manera bastante similar a una matriz numpy, pero no carga los datos en la memoria hasta que se divide. (Similar a memmap, pero no idéntico). Consulte lah5py
introducción para obtener más información.Cortar el conjunto de datos cargará un subconjunto de los datos en la memoria, pero presumiblemente querrá hacer algo con él, momento en el cual lo necesitará en la memoria de todos modos.
Si desea realizar cálculos fuera del núcleo, puede hacerlo con bastante facilidad para datos tabulares con
pandas
opytables
. Es posible conh5py
(más agradable para matrices ND grandes), pero debe bajar a un nivel más bajo y manejar la iteración usted mismo.Sin embargo, el futuro de los cálculos fuera del núcleo de tipo numpy es Blaze. Échale un vistazo si realmente quieres tomar esa ruta.
El caso "sin trozos"
En primer lugar, considere una matriz 3D ordenada en C escrita en el disco (la simularé llamando
arr.ravel()
e imprimiendo el resultado, para hacer las cosas más visibles):Los valores se almacenarían en el disco secuencialmente como se muestra en la línea 4 a continuación. (Por el momento, ignoremos los detalles del sistema de archivos y la fragmentación).
En el mejor de los casos, tomemos un corte a lo largo del primer eje. Observe que estos son solo los primeros 36 valores de la matriz. ¡Esta será una lectura muy rápida! (una búsqueda, una lectura)
De manera similar, el siguiente corte a lo largo del primer eje serán solo los siguientes 36 valores. Para leer un corte completo a lo largo de este eje, solo necesitamos una
seek
operación. Si todo lo que vamos a leer son varios cortes a lo largo de este eje, entonces esta es la estructura de archivo perfecta.Sin embargo, consideremos el peor de los casos: un corte a lo largo del último eje.
Para leer este segmento, necesitamos 36 búsquedas y 36 lecturas, ya que todos los valores están separados en el disco. ¡Ninguno de ellos es adyacente!
Esto puede parecer bastante menor, pero a medida que llegamos a arreglos cada vez más grandes, el número y tamaño de las
seek
operaciones crece rápidamente. Para una matriz 3D de gran tamaño (~ 10Gb) almacenada de esta manera y leída a través dememmap
, leer un corte completo a lo largo del eje "peor" puede llevar fácilmente decenas de minutos, incluso con hardware moderno. Al mismo tiempo, un corte a lo largo del mejor eje puede tardar menos de un segundo. Para simplificar, solo estoy mostrando cortes "completos" a lo largo de un solo eje, pero exactamente lo mismo sucede con cortes arbitrarios de cualquier subconjunto de datos.Por cierto, hay varios formatos de archivo que se aprovechan de esto y básicamente almacenan tres copias de enormes matrices 3D en el disco: una en orden C, una en orden F y una en el intermedio entre las dos. (Un ejemplo de esto es el formato D3D de Geoprobe, aunque no estoy seguro de que esté documentado en ninguna parte). ¿A quién le importa si el tamaño final del archivo es de 4 TB? ¡El almacenamiento es barato! Lo loco de esto es que debido a que el caso de uso principal es extraer un solo sub-segmento en cada dirección, las lecturas que desea hacer son muy, muy rápidas. ¡Funciona muy bien!
El simple caso "fragmentado"
Digamos que almacenamos "fragmentos" de 2x2x2 de la matriz 3D como bloques contiguos en el disco. En otras palabras, algo como:
Entonces, los datos en el disco se verían así
chunked
:Y solo para mostrar que son bloques de 2x2x2
arr
, observe que estos son los primeros 8 valores dechunked
:Para leer en cualquier corte a lo largo de un eje, leeríamos en 6 o 9 fragmentos contiguos (el doble de datos de los que necesitamos) y luego solo mantendríamos la parte que queríamos. Eso es un máximo de 9 búsquedas en el peor de los casos frente a un máximo de 36 búsquedas para la versión no fragmentada. (Pero el mejor caso sigue siendo 6 búsquedas frente a 1 para la matriz memmapped). Debido a que las lecturas secuenciales son muy rápidas en comparación con las búsquedas, esto reduce significativamente la cantidad de tiempo que se tarda en leer un subconjunto arbitrario en la memoria. Una vez más, este efecto aumenta con matrices más grandes.
HDF5 lleva esto unos pasos más allá. Los fragmentos no tienen que almacenarse contiguamente y están indexados por un B-Tree. Además, no es necesario que tengan el mismo tamaño en el disco, por lo que se puede aplicar compresión a cada fragmento.
Matrices fragmentadas con
h5py
De forma predeterminada,
h5py
no crea archivos HDF fragmentados en el disco (creo quepytables
sí, por el contrario).chunks=True
Sin embargo, si especifica al crear el conjunto de datos, obtendrá una matriz fragmentada en el disco.Como ejemplo rápido y mínimo:
Tenga en
chunks=True
cuenta que nos diceh5py
que elija automáticamente un tamaño de fragmento. Si sabe más sobre su caso de uso más común, puede optimizar el tamaño / forma del fragmento especificando una tupla de forma (por ejemplo,(2,2,2)
en el ejemplo simple anterior). Esto le permite hacer que las lecturas a lo largo de un eje particular sean más eficientes u optimizarlas para lecturas / escrituras de cierto tamaño.Comparación de rendimiento de E / S
Solo para enfatizar el punto, comparemos la lectura en porciones de un conjunto de datos HDF5 fragmentado y una matriz 3D grande (~ 8GB) ordenada por Fortran que contiene los mismos datos exactos.
He borrado todos los cachés del sistema operativo entre cada ejecución, por lo que estamos viendo el rendimiento "frío".
Para cada tipo de archivo, probaremos la lectura en un corte x "completo" a lo largo del primer eje y un corte z "completo" a lo largo del último eje. Para la matriz memmapped ordenada por Fortran, el segmento "x" es el peor de los casos y el segmento "z" es el mejor.
El código utilizado es en esencia (incluida la creación del
hdf
archivo). No puedo compartir fácilmente los datos utilizados aquí, pero podría simularlos mediante una matriz de ceros de la misma forma (621, 4991, 2600)
y tiponp.uint8
.El se
chunked_hdf.py
ve así:memmapped_array.py
es similar, pero tiene un toque más complejo para garantizar que los cortes se carguen realmente en la memoria (de forma predeterminada,memmapped
se devolvería otra matriz, que no sería una comparación de manzanas con manzanas).Primero echemos un vistazo al rendimiento de HDF:
Un corte x "completo" y un corte z "completo" toman aproximadamente la misma cantidad de tiempo (~ 20 segundos). Teniendo en cuenta que se trata de una matriz de 8 GB, no está nada mal. La mayor parte del tiempo
Y si comparamos esto con los tiempos de matriz de memmapped (está ordenado por Fortran: un "segmento z" es el mejor caso y un "segmento x" es el peor de los casos):
Sí, lo leiste bien. 0,3 segundos para una dirección de corte y ~ 3,5 horas para la otra.
El tiempo para cortar en la dirección "x" es mucho más largo que la cantidad de tiempo que tomaría cargar la matriz completa de 8GB en la memoria y seleccionar el corte que queríamos. (Nuevamente, esta es una matriz ordenada por Fortran. El tiempo de corte x / z opuesto sería el caso de una matriz ordenada en C).
Sin embargo, si siempre queremos tomar un corte en la mejor dirección, la gran matriz binaria en el disco es muy buena. (¡~ 0.3 segundos!)
Con una matriz memmapped, está atascado con esta discrepancia de E / S (o quizás anisotropía es un término mejor). Sin embargo, con un conjunto de datos HDF fragmentado, puede elegir el tamaño del fragmento de modo que el acceso sea igual o esté optimizado para un caso de uso particular. Te da mucha más flexibilidad.
En resumen
Con suerte, eso ayudará a aclarar una parte de su pregunta, en cualquier caso. HDF5 tiene muchas otras ventajas sobre los memmaps "crudos", pero no tengo espacio para ampliarlos todos aquí. La compresión puede acelerar algunas cosas (los datos con los que trabajo no se benefician mucho de la compresión, por lo que rara vez lo uso), y el almacenamiento en caché a nivel del sistema operativo a menudo se reproduce mejor con archivos HDF5 que con memmaps "sin procesar". Más allá de eso, HDF5 es un formato de contenedor realmente fantástico. Le brinda mucha flexibilidad a la hora de administrar sus datos y se puede utilizar desde más o menos cualquier lenguaje de programación.
En general, pruébelo y vea si funciona bien para su caso de uso. Creo que te sorprenderás.
fuente