2012-03-08 18 views
65

Sto cercando un modo veloce per preservare grandi array numpy. Voglio salvarli sul disco in un formato binario, quindi leggerli di nuovo in memoria relativamente velocemente. cPickle non è abbastanza veloce, sfortunatamente.il modo migliore per conservare gli array numpy sul disco

Ho trovato numpy.savez e numpy.load. Ma la cosa strana è, numpy.load carica un file npy in "memory-map". Ciò significa che la regolare manipolazione degli array è molto lenta. Ad esempio, qualcosa come questo sarebbe molto lento:

#!/usr/bin/python 
import numpy as np; 
import time; 
from tempfile import TemporaryFile 

n = 10000000; 

a = np.arange(n) 
b = np.arange(n) * 10 
c = np.arange(n) * -0.5 

file = TemporaryFile() 
np.savez(file,a = a, b = b, c = c); 

file.seek(0) 
t = time.time() 
z = np.load(file) 
print "loading time = ", time.time() - t 

t = time.time() 
aa = z['a'] 
bb = z['b'] 
cc = z['c'] 
print "assigning time = ", time.time() - t; 

più precisamente, la prima linea sarà molto veloce, ma le rimanenti linee che assegnano le matrici per obj sono incredibilmente lento:

loading time = 0.000220775604248 
assining time = 2.72940087318 

C'è un modo migliore per preservare gli array numpy? Idealmente, voglio essere in grado di memorizzare più array in un unico file.

+2

Per impostazione predefinita, 'np.load' dovrebbe * not * mmap il file. –

+5

Che dire di [pytables] (http://www.pytables.org)? – dsign

+0

@larsmans, grazie per la risposta. ma perché il tempo di ricerca (z ['a'] nel mio esempio di codice) è così lento? – CodeNoob

risposta

30

Sono un grande fan di hdf5 per la memorizzazione di grandi array numpy. Ci sono due opzioni per trattare con HDF5 in pitone:

http://www.pytables.org/

http://www.h5py.org/

Entrambi sono progettati per lavorare con gli array numpy in modo efficiente.

+12

saresti disposto a fornire un codice di esempio utilizzando questi pacchetti per salvare un array? – dbliss

+5

[h5py example] (http://stackoverflow.com/a/20938742/330558) e [esempio pytables] (http://stackoverflow.com/a/8843489/330558) –

+0

Dalle mie esperienze, le prestazioni di hdf5 lettura molto lenta e la scrittura con chunk storage e compressione abilitata. Ad esempio, ho due array 2D con forma (2500.000 * 2000) con dimensione del blocco (10.000 * 2000). Una singola operazione di scrittura di un array con forma (2000 * 2000) richiederà circa 1 ~ 2s per essere completata. Hai qualche suggerimento su come migliorare le prestazioni? grazie. –

12

savez() salva i dati in un file zip, potrebbe essere necessario un po 'di tempo per zip & decomprimere il file. È possibile utilizzare la funzione() & load() Save:

f = file("tmp.bin","wb") 
np.save(f,a) 
np.save(f,b) 
np.save(f,c) 
f.close() 

f = file("tmp.bin","rb") 
aa = np.load(f) 
bb = np.load(f) 
cc = np.load(f) 
f.close() 

Per salvare array multipli in un unico file, è sufficiente aprire il file prima, e poi salvare o caricare le matrici in sequenza.

29

C'è ora un clone basato su HDF5 di pickle chiamato hickle!

https://github.com/telegraphic/hickle

import hickle as hkl 

data = { 'name' : 'test', 'data_arr' : [1, 2, 3, 4] } 

# Dump data to file 
hkl.dump(data, 'new_data_file.hkl') 

# Load data from file 
data2 = hkl.load('new_data_file.hkl') 

print(data == data2) 

EDIT:

C'è anche la possibilità di "salamoia" direttamente in un archivio compresso facendo:

import pickle, gzip, lzma, bz2 

pickle.dump(data, gzip.open('data.pkl.gz', 'wb')) 
pickle.dump(data, lzma.open('data.pkl.lzma', 'wb')) 
pickle.dump(data, bz2.open('data.pkl.bz2', 'wb')) 

compression


Appendice

import numpy as np 
import matplotlib.pyplot as plt 
import pickle, os, time 
import gzip, lzma, bz2, h5py 

compressions = [ 'pickle', 'h5py', 'gzip', 'lzma', 'bz2' ] 
labels = [ 'pickle', 'h5py', 'pickle+gzip', 'pickle+lzma', 'pickle+bz2' ] 
size = 1000 

data = {} 

# Random data 
data['random'] = np.random.random((size, size)) 

# Not that random data 
data['semi-random'] = np.zeros((size, size)) 
for i in range(size): 
    for j in range(size): 
     data['semi-random'][i,j] = np.sum(data['random'][i,:]) + np.sum(data['random'][:,j]) 

# Not random data 
data['not-random'] = np.arange(size*size, dtype=np.float64).reshape((size, size)) 

sizes = {} 

for key in data: 

    sizes[key] = {} 

    for compression in compressions: 

     if compression == 'pickle': 
      time_start = time.time() 
      pickle.dump(data[key], open('data.pkl', 'wb')) 
      time_tot = time.time() - time_start 
      sizes[key]['pickle'] = (os.path.getsize('data.pkl') * 10**(-6), time_tot) 
      os.remove('data.pkl') 

     elif compression == 'h5py': 
      time_start = time.time() 
      with h5py.File('data.pkl.{}'.format(compression), 'w') as h5f: 
       h5f.create_dataset('data', data=data[key]) 
      time_tot = time.time() - time_start 
      sizes[key][compression] = (os.path.getsize('data.pkl.{}'.format(compression)) * 10**(-6), time_tot) 
      os.remove('data.pkl.{}'.format(compression)) 

     else: 
      time_start = time.time() 
      pickle.dump(data[key], eval(compression).open('data.pkl.{}'.format(compression), 'wb')) 
      time_tot = time.time() - time_start 
      sizes[key][ labels[ compressions.index(compression) ] ] = (os.path.getsize('data.pkl.{}'.format(compression)) * 10**(-6), time_tot) 
      os.remove('data.pkl.{}'.format(compression)) 


f, ax_size = plt.subplots() 
ax_time = ax_size.twinx() 

x_ticks = labels 
x = np.arange(len(x_ticks)) 

y_size = {} 
y_time = {} 
for key in data: 
    y_size[key] = [ sizes[key][ x_ticks[i] ][0] for i in x ] 
    y_time[key] = [ sizes[key][ x_ticks[i] ][1] for i in x ] 

width = .2 
viridis = plt.cm.viridis 

p1 = ax_size.bar(x-width, y_size['random']  , width, color = viridis(0) ) 
p2 = ax_size.bar(x  , y_size['semi-random'] , width, color = viridis(.45)) 
p3 = ax_size.bar(x+width, y_size['not-random'] , width, color = viridis(.9)) 

p4 = ax_time.bar(x-width, y_time['random'] , .02, color = 'red') 
ax_time.bar(x  , y_time['semi-random'] , .02, color = 'red') 
ax_time.bar(x+width, y_time['not-random'] , .02, color = 'red') 

ax_size.legend((p1, p2, p3, p4), ('random', 'semi-random', 'not-random', 'saving time'), loc='upper center',bbox_to_anchor=(.5, -.1), ncol=4) 
ax_size.set_xticks(x) 
ax_size.set_xticklabels(x_ticks) 

f.suptitle('Pickle Compression Comparison') 
ax_size.set_ylabel('Size [MB]') 
ax_time.set_ylabel('Time [s]') 

f.savefig('sizes.pdf', bbox_inches='tight') 
+3

Questa è una bella scoperta, o almeno una bella idea. –

4

Un'altra possibilità di memorizzare le matrici NumPy in modo efficiente è Bloscpack:

#!/usr/bin/python 
import numpy as np 
import bloscpack as bp 
import time 

n = 10000000 

a = np.arange(n) 
b = np.arange(n) * 10 
c = np.arange(n) * -0.5 
tsizeMB = sum(i.size*i.itemsize for i in (a,b,c))/2**20. 

blosc_args = bp.DEFAULT_BLOSC_ARGS 
blosc_args['clevel'] = 6 
t = time.time() 
bp.pack_ndarray_file(a, 'a.blp', blosc_args=blosc_args) 
bp.pack_ndarray_file(b, 'b.blp', blosc_args=blosc_args) 
bp.pack_ndarray_file(c, 'c.blp', blosc_args=blosc_args) 
t1 = time.time() - t 
print "store time = %.2f (%.2f MB/s)" % (t1, tsizeMB/t1) 

t = time.time() 
a1 = bp.unpack_ndarray_file('a.blp') 
b1 = bp.unpack_ndarray_file('b.blp') 
c1 = bp.unpack_ndarray_file('c.blp') 
t1 = time.time() - t 
print "loading time = %.2f (%.2f MB/s)" % (t1, tsizeMB/t1) 

e l'uscita per il mio portatile (un tempo relativamente vecchio MacBook Air con un processore Core2):

$ python store-blpk.py 
store time = 0.19 (1216.45 MB/s) 
loading time = 0.25 (898.08 MB/s) 

significa che può essere memorizzato molto velocemente, ovvero il collo di bottiglia è in genere il disco. Tuttavia, poiché i rapporti di compressione sono piuttosto buoni qui, la velocità effettiva viene moltiplicata per i rapporti di compressione. Qui ci sono i formati per questi array 76 MB:

$ ll -h *.blp 
-rw-r--r-- 1 faltet staff 921K Mar 6 13:50 a.blp 
-rw-r--r-- 1 faltet staff 2.2M Mar 6 13:50 b.blp 
-rw-r--r-- 1 faltet staff 1.4M Mar 6 13:50 c.blp 

Si prega di notare che l'uso del compressore Blosc è fondamentale per il raggiungimento di questo. Lo stesso script ma usando 'clevel' = 0 (cioè compressione disabilitazione):

$ python bench/store-blpk.py 
store time = 3.36 (68.04 MB/s) 
loading time = 2.61 (87.80 MB/s) 

è chiaramente un collo di bottiglia per le prestazioni del disco.

+2

A chi può interessare: Sebbene Bloscpack e PyTables siano progetti diversi, il primo si concentra solo sul dump del disco e sull'array di matrici non memorizzate, ho provato entrambi e per i puri "progetti di file dump" Bloscpack è quasi 6 volte più veloce di PyTables. – MSardelich

5

Il tempo di ricerca è lento perché quando si utilizza mmap per non caricare il contenuto della matrice in memoria quando si richiama il metodo load. I dati sono pigri quando sono necessari dati particolari. E questo accade nella ricerca nel tuo caso. Ma la seconda ricerca non sarà così lenta.

Questa è una bella funzionalità di mmap quando si dispone di un grande array non è necessario caricare interi dati in memoria.

per risolvere il tuo può usare joblib è possibile scaricare qualsiasi oggetto che si desidera utilizzare joblib.dump anche due o più numpy arrays, vedere l'esempio prestazioni

firstArray = np.arange(100) 
secondArray = np.arange(50) 
# I will put two arrays in dictionary and save to one file 
my_dict = {'first' : firstArray, 'second' : secondArray} 
joblib.dump(my_dict, 'file_name.dat') 
70

Ho confrontato (spazio e tempo) per un certo numero di modi per memorizzare gli array numpy. Pochi di loro supportano più array per file, ma forse è comunque utile.

benchmark for numpy array storage

file binari NPY e sono entrambi molto veloce e piccolo per i dati densi. Se i dati sono sparsi o molto strutturati, potresti voler usare npz con la compressione, il che consente di risparmiare molto spazio ma ha un costo di caricamento.

Se la portabilità è un problema, binario è meglio di npy. Se la leggibilità umana è importante, allora dovrai sacrificare un sacco di prestazioni, ma può essere raggiunto abbastanza bene usando csv (che è anche molto portabile, naturalmente).

Maggiori dettagli e il codice sono disponibili a the github repo.

+5

sei il vero mvp – pietz

+0

Puoi spiegare perché 'binary' è meglio di' npy' per la portabilità? Questo vale anche per 'npz'? – daniel451

+1

@ daniel451 Perché qualsiasi lingua può leggere file binari se solo conoscono la forma, il tipo di dati e se si tratta di righe o colonne. Se stai usando Python, allora npy va bene, probabilmente è un po 'più facile del binario. – Mark

Problemi correlati