python - Forma óptima de fragmento de conjunto de datos HDF5 para leer filas
performance dataset (1)
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). Leeré una selección de filas (digamos ~ 1000 filas) muchas veces, ubicadas en el conjunto de datos. Entonces no puedo usar x: (x + 1000) para cortar filas.
La lectura de filas desde HDF5 sin memoria ya es lenta usando h5py ya que tengo que pasar una lista ordenada y recurrir a una indexación elegante. ¿Hay alguna manera 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 fragmentos de 1MB-10MB y elegir la forma consistente de lo que estoy leyendo. Sin embargo, la creación de una gran cantidad de archivos HDF5 con diferentes formas de fragmentos para pruebas es computacionalmente costosa y muy lenta.
Para cada selección de ~ 1,000 filas, las sumo inmediatamente para obtener una matriz de longitud 10,000. 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:
- Reescribir el conjunto de datos con forma de fragmento (128, 10000), que calculo que es de ~ 5 MB, es prohibitivamente lento.
- 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 correcto de caché de fragmentos
Al principio no quiero discutir algunas cosas generales. Es muy importante saber que cada fragmento individual solo se puede leer o escribir como un todo. El tamaño estándar de caché de fragmentos de h5py que puede evitar E / S de disco excesivas es de solo un MB por defecto y, en muchos casos, debería aumentarse, lo que se discutirá más adelante.
Como ejemplo:
- Tenemos un dset con forma (639038, 10000), float32 (25,5 GB sin comprimir)
-
no queremos escribir nuestra columna de datos sabio
dset[:,i]=arr
y leerlo en fila sabioarr=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á demasiado mala (aunque el tamaño del fragmento es un poco pequeño) porque leemos solo los datos que estamos utilizando. 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 en cualquier otro momento. Esto se debe a que si modifica un fragmento, primero debe leerlo si no está almacenado en caché (supongo un tamaño de caché de fragmento 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.
Una suposición que dará velocidad decente / lectura y escritura:
- Elegimos un tamaño de fragmento de (100, 1000)
- Si queremos iterar sobre la primera Dimensión, necesitamos al menos (1000 * 639038 * 4 -> 2,55 GB) de caché para evitar sobrecarga adicional de E / S como se describió anteriormente y (100 * 10000 * 4 -> 0,4 MEGABYTE).
- Por lo tanto, deberíamos proporcionar al menos 2,6 GB de caché de datos fragmentados en este ejemplo. Esto se puede hacer fácilmente con h5py-cache https://pypi.python.org/pypi/h5py-cache/1.0
Conclusión En general, no hay un tamaño o forma de trozo correcto, depende en gran medida de la tarea cuál utilizar. Nunca elija el tamaño o la forma de su fragmento sin pensar en el caché de fragmentos. RAM es órdenes de magnite más rápido que el SSD más rápido en lo que respecta a lectura / escritura aleatoria.
Con respecto a su problema , simplemente leería las filas aleatorias, el tamaño incorrecto del trozo de caché es su verdadero problema.
Compare el rendimiento del siguiente código con su 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 corte elegante
Escribí en los comentarios que no podía ver este comportamiento en las versiones recientes. Estaba equivocado. Compare 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.