Forma óptima del fragmento de datos HDF5 para la lectura de filas

Tengo un conjunto de datos HDF5 de tamaño razonable (18 GB comprimido) y estoy buscando optimizar las filas de lectura para la velocidad. La forma es (639038, 10000). Estaré leyendo una selección de filas (por ejemplo, ~ 1000 filas) muchas veces, ubicadas en el conjunto de datos. Entonces no puedo usar x: (x + 1000) para dividir filas.

La lectura de filas de HDF5 sin memoria ya es lenta con h5py, ya que tengo que pasar una lista ordenada y recurrir a la indexación elegante. ¿Hay alguna forma de evitar la indexación elegante o hay una mejor forma / tamaño de trozo que pueda usar?

He leído reglas generales, como tamaños de trozos de 1MB-10MB y elección de forma consistente con lo que estoy leyendo. Sin embargo, la creación de un gran número de archivos HDF5 con diferentes formas de trozos para la prueba es computacionalmente costosa y muy lenta.

Para cada selección de ~ 1,000 filas, las sumo inmediatamente para obtener una matriz de 10,000 longitudes. Mi conjunto de datos actual se ve así:

'10000': {'chunks': (64, 1000), 'compression': 'lzf', 'compression_opts': None, 'dtype': dtype('float32'), 'fillvalue': 0.0, 'maxshape': (None, 10000), 'shape': (639038, 10000), 'shuffle': False, 'size': 2095412704} 

Lo que ya he probado:

  • La reescritura del conjunto de datos con forma de trozo (128, 10000), que calculo en ~ 5MB, es prohibitivamente lenta.
  • Miré a dask.array para optimizar, pero como ~ 1.000 filas caben fácilmente en la memoria no vi ningún beneficio.

Encontrar el tamaño de caché de trozo correcto

Al principio no iba a discutir algunas cosas generales. Es muy importante saber que cada fragmento individual solo se puede leer o escribir en su totalidad. El tamaño estándar de caché de caché de h5py que puede evitar el exceso de E / S del disco es de solo un MB por defecto y, en muchos casos, debería incrementarse, lo que se explicará más adelante.

Como ejemplo:

  • Tenemos un dset con forma (639038, 10000), float32 (25,5 GB sin comprimir)
  • no deberíamos escribir nuestra columna de datos dset[:,i]=arr y leerla fila sabio arr=dset[i,:]
  • Elegimos una forma de trozo completamente incorrecta para este tipo de trabajo, es decir (1,10000)

En este caso, la velocidad de lectura no será mala (aunque el tamaño del fragmento es un poco pequeño) porque solo leemos los datos que estamos usando. Pero, ¿qué sucede cuando escribimos en ese conjunto de datos? Si accedemos a una columna, se escribe un número de coma flotante de cada fragmento. Esto significa que en realidad estamos escribiendo todo el conjunto de datos (25,5 GB) con cada iteración y leemos todo el conjunto de datos cada dos veces. Esto se debe a que si modifica un fragmento, primero debe leerlo si no está guardado en la caché (supongo que el tamaño de caché de un fragmento es inferior a 25,5 GB aquí).

Entonces, ¿qué podemos mejorar aquí? En tal caso, tenemos que hacer un compromiso entre la velocidad de escritura / lectura y la memoria que utiliza el caché de fragmentos.

Un supuesto que dará tanto velocidad de lectura / escritura decente:

  • Elegimos un tamaño de trozo de (100, 1000)
  • Si no queremos recorrer la primera dimensión, necesitamos al menos un caché (1000 * 639038 * 4 -> 2,55 GB) para evitar la sobrecarga adicional de IO como se describe anteriormente y (100 * 10000 * 4 -> 0,4 MEGABYTE).
  • Por lo tanto, deberíamos proporcionar al menos 2,6 GB de caché de datos en este ejemplo. Esto se puede hacer fácilmente con h5py-cache https://pypi.python.org/pypi/h5py-cache/1.0

Conclusión No hay una forma o tamaño de trozo generalmente correcto, depende en gran medida de la tarea que uno utilice. Nunca elija el tamaño o la forma del trozo sin pensar en el caché de trozos. La RAM es una orden de magnite más rápida que la SSD más rápida en lo que respecta a la lectura / escritura aleatoria.

Con respecto a su problema , simplemente leería las filas aleatorias, el problema es el tamaño inadecuado de caché de caché.

Compara el rendimiento del siguiente código con tu versión:

 import h5py as h5 import time import numpy as np import h5py_cache as h5c def ReadingAndWriting(): File_Name_HDF5='Test.h5' shape = (639038, 10000) chunk_shape=(100, 1000) Array=np.array(np.random.rand(shape[0]),np.float32) #We are using 4GB of chunk_cache_mem here f = h5c.File(File_Name_HDF5, 'w',chunk_cache_mem_size=1024**2*4000) d = f.create_dataset('Test', shape ,dtype='f',chunks=chunk_shape,compression="lzf") #Writing columns t1=time.time() for i in xrange(0,shape[1]): d[:,i:i+1]=np.expand_dims(Array, 1) f.close() print(time.time()-t1) # Reading random rows # If we read one row there are actually 100 read, but if we access a row # which is already in cache we would see a huge speed up. f = h5c.File(File_Name_HDF5,'r',chunk_cache_mem_size=1024**2*4000) d = f["Test"] for j in xrange(0,639): t1=time.time() # With more iterations it will be more likely that we hit a already cached row inds=np.random.randint(0, high=shape[0]-1, size=1000) for i in xrange(0,inds.shape[0]): Array=np.copy(d[inds[i],:]) print(time.time()-t1) f.close() if __name__ == "__main__": ReadingAndWriting() 

La forma más simple de rebanar de fantasía.

Escribí en los comentarios, que no podía ver este comportamiento en versiones recientes. Estaba equivocado. Compara lo siguiente:

 import h5py as h5 import time import numpy as np import h5py_cache as h5c def Writing(): File_Name_HDF5='Test.h5' shape = (63903, 10000) Array=np.array(np.random.rand(shape[0]),np.float32) # Writing_1 normal indexing f = h5c.File(File_Name_HDF5, 'w',chunk_cache_mem_size=1024**3) d = f.create_dataset('Test', shape ,dtype='f',chunks=(10000,shape[1]/50)) t1=time.time() for i in xrange(0,shape[1]): d[:,i:i+1]=np.expand_dims(Array,1) f.close() print(time.time()-t1) # Writing_2 simplest form of fancy indexing f = h5c.File(File_Name_HDF5, 'w',chunk_cache_mem_size=1024**3) d = f.create_dataset('Test', shape ,dtype='f',chunks=(10000,shape[1]/50)) t1=time.time() for i in xrange(0,shape[1]): d[:,i]=Array f.close() print(time.time()-t1) if __name__ == "__main__": Writing() 

Esto le da a mi SSD 10,8 segundos para la primera versión y 55 segundos para la segunda versión.