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

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

我找到了 笨蛋 Savez笨蛋,装货。但奇怪的是 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

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

137545 次浏览

我非常喜欢用 hdf5存储大型数组。在 python 中处理 hdf5有两种选择:

Http://www.pytables.org/

Http://www.h5py.org/

这两种方法都是为了有效地处理数字数组而设计的。

Savez ()将数据保存在压缩文件中,可能需要一些时间来压缩和解压缩文件。可以使用 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()

要在一个文件中保存多个数组,您只需要首先打开该文件,然后按顺序保存或加载这些数组。

现在有一个基于 HDF5的 pickle克隆称为 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)

编辑:

也有可能通过以下方法直接“腌制”到压缩的档案中:

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


附录

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


compressions = ['pickle', 'h5py', 'gzip', 'lzma', 'bz2']
modules = dict(
pickle=pickle, h5py=h5py, gzip=gzip, lzma=lzma, bz2=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:
path = 'data.pkl.{}'.format(compression)


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


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


else:
time_start = time.time()
with modules[compression].open(path, 'wb') as fout:
pickle.dump(data[key], fout)
time_tot = time.time() - time_start
sizes[key][labels[compressions.index(compression)]] = (
os.path.getsize(path) * 10**-6,
time_tot,
)
os.remove(path)




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

有效存储数字数组的另一种可能性是 布洛斯帕克:

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

以及我笔记本电脑的输出(相对较老的配备 Core2处理器的 MacBook Air) :

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

这意味着它可以存储非常快,也就是说,瓶颈通常是磁盘。然而,由于这里的压缩比非常好,有效速度乘以压缩比。下面是这些76MB 数组的大小:

$ 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

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

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

显然是由于磁盘性能的瓶颈。

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

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

为了解决你可以使用 即兴发挥你可以转储任何对象,你想使用 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')

我比较了许多存储数字数组的方法的性能(空间和时间)。它们中很少有支持每个文件多个数组的,但是无论如何它可能是有用的。

benchmark for numpy array storage

对于密集数据来说,Npy 和二进制文件都非常快而且非常小。如果数据非常稀疏或非常结构化,那么可能需要使用带有压缩的 npz,这将节省大量空间,但会占用一些加载时间。

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

更多细节和代码可在 Github 回购上获得。

“最好”取决于你的目标是什么。正如其他人所说,二进制文件具有最大的可移植性,但问题是您需要了解数据是如何存储的。

Darr 以基于平面二进制文件和文本文件的自记录方式保存 numpy 数组。这最大限度地提高了可读性。它还自动包含用各种数据科学语言读取数组的代码,例如 numpy 本身,还包括 R、 Matlab、 Julia 等。

披露: 我写了图书馆。