在磁盘上保存numpy数组的最佳方法

我正在寻找一个快速的方法来保存大的numpy数组。 我想将它们以二进制格式保存到磁盘,然后相对快速地将它们读回到内存中。 cPickle不够快,不幸的是。

我发现numpy.savez和numpy.load 。 但奇怪的是,numpy.load将一个npy文件加载到“memory-map”中。 这意味着对数组的操作确实很慢。 例如,这样的事情会很慢:

#!/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; 

更准确地说,第一行会非常快,但是将数组分配给obj的其余行却是非常慢的:

 loading time = 0.000220775604248 assining time = 2.72940087318 

有没有更好的方法来保存numpy数组? 理想情况下,我想能够在一个文件中存储多个数组。

我是hdf5存储大型numpyarrays的忠实粉丝。 在python中有两种处理hdf5的选项:

http://www.pytables.org/

http://www.h5py.org/

两者都devise用于有效地使用numpy数组。

我比较了性能(空间和时间)的一些方法来存储numpy数组。 他们几乎没有支持每个文件多个数组,但也许它是有用的。

numpy数组存储的基准

对于密集数据,Npy和二进制文件都非常快速和小巧。 如果数据稀疏或非常结构化,则可能需要使用npz进行压缩,这将节省大量空间,但会花费一些加载时间。

如果可移植性是一个问题,二进制比npy好。 如果人的可读性是重要的,那么你将不得不牺牲很多的性能,但是使用csv可以很好地实现(当然这也是非常便携的)。

更多的细节和代码可以在github回购 。

现在有一个叫做hickle基于HDF5的pickle克隆!

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 ) 

编辑:

还有可能通过以下方式直接“压缩”到压缩档案中:

 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' ) ) 

压缩


附录

 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' ) 

savez()将数据保存在一个zip文件中,可能需要一些时间来压缩和解压缩文件。 你可以使用save()和load()函数:

 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() 

要将多个数组保存到一个文件中,只需要先打开文件,然后按顺序保存或加载数组。

查找时间很慢,因为当您调用load方法时,使用mmap不会将数组的内容加载到内存中。 当需要特定的数据时,数据被延迟加载。 这发生在你的情况查找。 但是第二次查询不会那么慢。

这是mmap一个很好的特性,当你有一个大数组时,你不需要把整个数据加载到内存中。

为了解决你可以使用joblib,你可以使用joblib.dump转储任何你想要的对象,即使是两个或更多numpy arrays ,看例子

 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') 

Bloscpack的另一种有效存储numpyarrays的可能性是:

 #!/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) 

和我的笔记本电脑的输出(一个相对较旧的MacBook Air与Core2处理器):

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

这意味着它可以存储非常快,即瓶颈通常是磁盘。 但是,由于这里的压缩比非常好,所以有效速度乘以压缩比。 这些是76 MBarrays的大小:

 $ 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 

请注意,使用Blosc压缩机是实现这一目标的基础。 相同的脚本,但使用'clevel'= 0(即禁用压缩):

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

显然是磁盘性能瓶颈。