Las opciones de guardado y carga más rápidas para una matriz numpy

Tengo un script que genera numpy array numpy dos dimensiones con dtype=float y shape en el orden de (1e3, 1e6) . Ahora mismo estoy usando np.save y np.load para realizar operaciones de E / S con los arreglos. Sin embargo, estas funciones toman varios segundos para cada arreglo. ¿Existen métodos más rápidos para guardar y cargar los arreglos completos (es decir, sin hacer suposiciones sobre sus contenidos y reducirlos)? Estoy abierto a convertir la array a otro tipo antes de guardar siempre que los datos se conserven exactamente.

Para arreglos realmente grandes, he escuchado sobre varias soluciones, y en su mayoría sobre la pereza en la E / S:

  • NumPy.memmap , mapea grandes arrays a forma binaria
    • Pros:
      • Ninguna dependencia que no sea Numpy
      • Reemplazo transparente de ndarray (cualquier clase que acepte ndarray acepta memmap )
    • Contras :
      • Los trozos de su matriz están limitados a 2.5G.
      • Todavía limitado por el rendimiento de Numpy
  • Utilice enlaces Python para HDF5, un formato de archivo preparado para bigdata , como PyTables o h5py

    • Pros:
      • El formato admite compresión, indexación y otras características súper agradables
      • Al parecer, el último formato de archivo PetaByte-large
    • Contras :
      • ¿Curva de aprendizaje de tener un formato jerárquico?
      • Debe definir cuáles son sus necesidades de rendimiento (ver más adelante)
  • Sistema de decapado de Python (fuera de la carrera, mencionado por Pythonicity en lugar de velocidad)

    • Pros:
      • ¡Es python! (jaja)
      • Soporta todo tipo de objetos.
    • Contras:
      • Probablemente más lento que los demás (porque apuntaba a cualquier objeto, no a matrices)

Numpy.memmap

De los documentos de NumPy.memmap :

Cree un mapa de memoria a una matriz almacenada en un archivo binario en el disco.

Los archivos asignados en memoria se utilizan para acceder a pequeños segmentos de archivos grandes en el disco, sin tener que leer todo el archivo en la memoria

El objeto memmap se puede utilizar en cualquier lugar donde se acepte un ndarray. Dado cualquier memmap fp , isinstance(fp, numpy.ndarray) devuelve True.


Arreglos HDF5

Desde el doc h5py

Te permite almacenar grandes cantidades de datos numéricos y manipular fácilmente esos datos desde NumPy. Por ejemplo, puede dividir en conjuntos de datos de varios terabytes almacenados en el disco, como si fueran matrices NumPy reales. Miles de conjuntos de datos pueden almacenarse en un solo archivo, categorizarse y etiquetarse como lo desee.

El formato admite la compresión de datos de varias maneras (más bits cargados para la misma lectura de E / S), pero esto significa que los datos se vuelven menos fáciles de consultar individualmente, pero en su caso (simplemente cargando / volcando matrices) podría ser eficiente

Aquí hay una comparación con PyTables.

No puedo acceder a (int(1e3), int(1e6) debido a restricciones de memoria. Por lo tanto, utilicé una matriz más pequeña:

 data = np.random.random((int(1e3), int(1e5))) 

NumPy save :

 %timeit np.save('array.npy', data) 1 loops, best of 3: 4.26 s per loop 

NumPy load :

 %timeit data2 = np.load('array.npy') 1 loops, best of 3: 3.43 s per loop 

Escritura de PyTables:

 %%timeit with tables.open_file('array.tbl', 'w') as h5_file: h5_file.create_array('/', 'data', data) 1 loops, best of 3: 4.16 s per loop 

Lectura de PyTables:

  %%timeit with tables.open_file('array.tbl', 'r') as h5_file: data2 = h5_file.root.data.read() 1 loops, best of 3: 3.51 s per loop 

Los números son muy similares. Así que no hay ganancia real con PyTables aquí. Pero estamos muy cerca de la tasa máxima de escritura y lectura de mi SSD.

Escritura:

 Maximum write speed: 241.6 MB/s PyTables write speed: 183.4 MB/s 

Leyendo:

 Maximum read speed: 250.2 PyTables read speed: 217.4 

La compresión realmente no ayuda debido a la aleatoriedad de los datos:

 %%timeit FILTERS = tables.Filters(complib='blosc', complevel=5) with tables.open_file('array.tbl', mode='w', filters=FILTERS) as h5_file: h5_file.create_carray('/', 'data', obj=data) 1 loops, best of 3: 4.08 s per loop 

La lectura de los datos comprimidos se vuelve un poco más lenta:

 %%timeit with tables.open_file('array.tbl', 'r') as h5_file: data2 = h5_file.root.data.read() 1 loops, best of 3: 4.01 s per loop 

Esto es diferente para los datos regulares:

  reg_data = np.ones((int(1e3), int(1e5))) 

La escritura es significativamente más rápida:

 %%timeit FILTERS = tables.Filters(complib='blosc', complevel=5) with tables.open_file('array.tbl', mode='w', filters=FILTERS) as h5_file: h5_file.create_carray('/', 'reg_data', obj=reg_data) 

1 bucles, lo mejor de 3: 849 ms por bucle

Lo mismo ocurre con la lectura:

 %%timeit with tables.open_file('array.tbl', 'r') as h5_file: reg_data2 = h5_file.root.reg_data.read() 1 loops, best of 3: 1.7 s per loop 

Conclusión : cuanto más regulares sean sus datos, más rápido debería ser el uso de PyTables.

Según mi experiencia, np.save () & np.load () es la solución más rápida cuando se transfieren datos entre el disco duro y la memoria hasta el momento. He confiado mucho en mi carga de datos en la base de datos y en el sistema HDFS antes de darme cuenta de esta conclusión. Mis pruebas muestran que: El ancho de banda de carga de datos de la base de datos (desde el disco duro a la memoria) podría ser de alrededor de 50 MBps (Byets / Second), pero el ancho de banda np.load () es casi igual al ancho de banda máximo de mi disco duro: 2GBps (Byets / Segundo). Ambos entornos de prueba utilizan la estructura de datos más simple.

Y no creo que sea un problema usar varios segundos para cargar una matriz con forma: (1e3, 1e6). Por ejemplo, la forma de su matriz es (1000, 1000000), su tipo de datos es float128, entonces el tamaño de los datos puros es (128/8) * 1000 * 1,000,000 = 16,000,000,000 = 16GBytes y si toma 4 segundos, entonces su ancho de banda de carga de datos es de 16GBytes / 4segundos = 4GBps. El ancho de banda máximo de SATA3 es 600MBps = 0.6GBps, su ancho de banda de carga de datos ya es 6 veces mayor, su rendimiento de carga de datos casi podría competir con el ancho de banda máximo de DDR , ¿qué más desea?

Así que mi conclusión final es:

No use Pickle de python, no use ninguna base de datos, no use ningún sistema de big data para almacenar sus datos en el disco duro, si pudiera usar np.save () y np.load (). Estas dos funciones son la solución más rápida para transferir datos entre el disco duro y la memoria hasta el momento.

También probé el HDF5 y descubrí que es mucho más lento que np.load () y np.save (), así que usa np.save () & np.load () si tienes suficiente memoria DDR en tu plataforma.