numpy数组最快的保存和加载选项

dbl*_*iss 35 python arrays io performance numpy

我有一个脚本,生成二维numpy arrays dtype=float和形状的顺序(1e3, 1e6).现在我正在使用np.save并使用np.load数组执行IO操作.但是,每个阵列的这些功能需要几秒钟.是否有更快的方法来保存和加载整个数组(即,不对其内容进行假设并减少它们)?array只要数据保留完全,我就可以在保存之前将s 转换为另一种类型.

Jib*_*iby 38

对于非常大的数组,我听说过几个解决方案,而且他们主要是在I/O上懒惰:

  • NumPy.memmap,将大数组映射到二进制形式
    • 优点:
      • 没有Numpy以外的依赖
      • 透明替换ndarray(接受ndarray接受的任何类memmap)
    • 缺点:
      • 阵列的块数限制为2.5G
      • 仍然受到Numpy吞吐量的限制
  • 使用Python绑定HDF5,这是一种支持大数据的文件格式,如PyTablesh5py

    • 优点:
      • 格式支持压缩,索引和其他超级好的功能
      • 显然是最终的PetaByte大文件格式
    • 缺点:
      • 具有分层格式的学习曲线?
      • 必须定义您的性能需求(见后文)
  • Python的酸洗系统(在比赛中,提到Pythonicity而不是速度)

    • 优点:
      • 这是Pythonic!(哈哈)
      • 支持各种对象
    • 缺点:
      • 可能比其他人慢(因为针对任何对象而不是数组)

Numpy.memmap

来自NumPy.memmap的文档:

为存储在磁盘上的二进制文件中的数组创建内存映射.

内存映射文件用于访问磁盘上的大段文件,而无需将整个文件读入内存

memmap对象可以在接受ndarray的任何地方使用.给定任何memmap fp,isinstance(fp, numpy.ndarray) 返回True.


HDF5阵列

来自h5py doc

允许您存储大量数值数据,并轻松操作NumPy中的数据.例如,您可以切片存储在磁盘上的多TB数据集,就好像它们是真正的NumPy数组一样.成千上万的数据集可以存储在一个文件中,无论您需要如何分类和标记.

该格式支持以各种方式压缩数据(为相同的I/O读取加载更多位),但这意味着数据变得不那么容易单独查询,但在您的情况下(纯粹加载/转储数组)它可能是有效的


Mik*_*ler 21

这是与PyTables的比较.

(int(1e3), int(1e6)由于内存限制,我无法起床.因此,我使用了一个较小的数组:

data = np.random.random((int(1e3), int(1e5)))
Run Code Online (Sandbox Code Playgroud)

NumPy save:

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

NumPy load:

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

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

数字非常相似.因此PyTables在这里没有真正的好处.但我们非常接近我的SSD的最大写入和读取速率.

写作:

Maximum write speed: 241.6 MB/s
PyTables write speed: 183.4 MB/s
Run Code Online (Sandbox Code Playgroud)

读:

Maximum read speed: 250.2
PyTables read speed: 217.4
Run Code Online (Sandbox Code Playgroud)

由于数据的随机性,压缩并没有真正帮助:

%%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
Run Code Online (Sandbox Code Playgroud)

读取压缩数据变得有点慢:

%%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
Run Code Online (Sandbox Code Playgroud)

这与常规数据不同:

 reg_data = np.ones((int(1e3), int(1e5)))
Run Code Online (Sandbox Code Playgroud)

写作速度明显加快:

%%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)
Run Code Online (Sandbox Code Playgroud)

1个循环,最佳3:849 ms每个循环

阅读也是如此:

%%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
Run Code Online (Sandbox Code Playgroud)

结论:使用PyTables时,您的数据越频繁.


Nic*_*mer 21

我已经使用perfplot(我的项目之一)比较了几种方法。结果如下:

写作

在此处输入图片说明

对于大型数组,所有方法的速度都差不多。文件大小也相同,这是可以预料的,因为输入数组是随机双精度数,因此几乎不可压缩。

重现情节的代码:

import perfplot
import pickle
import numpy
import h5py
import tables
import zarr


def npy_write(data):
    numpy.save("npy.npy", data)


def hdf5_write(data):
    f = h5py.File("hdf5.h5", "w")
    f.create_dataset("data", data=data)


def pickle_write(data):
    with open("test.pkl", "wb") as f:
        pickle.dump(data, f)


def pytables_write(data):
    f = tables.open_file("pytables.h5", mode="w")
    gcolumns = f.create_group(f.root, "columns", "data")
    f.create_array(gcolumns, "data", data, "data")
    f.close()


def zarr_write(data):
    zarr.save("out.zarr", data)


perfplot.save(
    "write.png",
    setup=numpy.random.rand,
    kernels=[npy_write, hdf5_write, pickle_write, pytables_write, zarr_write],
    n_range=[2 ** k for k in range(28)],
    xlabel="len(data)",
    equality_check=None,
)
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

pickles、pytables 和 hdf5 的速度大致相同;对于大型数组,pickles 和 zarr 速度较慢。

重现情节的代码:

import perfplot
import pickle
import numpy
import h5py
import tables
import zarr


def setup(n):
    data = numpy.random.rand(n)
    # write all files
    #
    numpy.save("out.npy", data)
    #
    f = h5py.File("out.h5", "w")
    f.create_dataset("data", data=data)
    f.close()
    #
    with open("test.pkl", "wb") as f:
        pickle.dump(data, f)
    #
    f = tables.open_file("pytables.h5", mode="w")
    gcolumns = f.create_group(f.root, "columns", "data")
    f.create_array(gcolumns, "data", data, "data")
    f.close()
    #
    zarr.save("out.zip", data)


def npy_read(data):
    return numpy.load("out.npy")


def hdf5_read(data):
    f = h5py.File("out.h5", "r")
    out = f["data"][()]
    f.close()
    return out


def pickle_read(data):
    with open("test.pkl", "rb") as f:
        out = pickle.load(f)
    return out


def pytables_read(data):
    f = tables.open_file("pytables.h5", mode="r")
    out = f.root.columns.data[()]
    f.close()
    return out


def zarr_read(data):
    return zarr.load("out.zip")


perfplot.show(
    setup=setup,
    kernels=[
        npy_read,
        hdf5_read,
        pickle_read,
        pytables_read,
        zarr_read,
    ],
    n_range=[2 ** k for k in range(28)],
    xlabel="len(data)",
)
Run Code Online (Sandbox Code Playgroud)


Clo*_*ONG 8

根据我的经验,到目前为止,在硬盘和内存之间传输数据时,np.save()和np.load()是最快的解决方案.在我意识到这个结论之前,我非常依赖我在数据库和HDFS系统上的数据加载.我的测试表明:该数据库的数据加载(从硬盘复制到内存)带宽可50Mbps左右(Byets /秒),但np.load()带宽是我的硬盘最大带宽几乎相同:2Gbps的(Byets /第二).两个测试环境都使用最简单的数据结构.

并且我认为使用几秒钟加载具有形状的数组不是一个问题:(1e3,1e6).例如你的数组形状是(1000,1000000),它的数据类型是float128,那么纯数据大小是(128/8)*1000*1,000,000 = 16,000,000,000 = 16GBytes,如果需要4秒,那么你的数据加载带宽是16GBytes/4Seconds = 4GBps.SATA3最大带宽为600MBps = 0.6GBps,您的数据加载带宽已经是其6倍,您的数据加载性能几乎可以与DDR的最大带宽竞争,您还想要什么?

所以我的最终结论是:

如果可以使用np.save()和np.load(),请不要使用python的Pickle,不要使用任何数据库,不要使用任何大数据系统将数据存储到硬盘中.到目前为止,这两个函数是在硬盘和内存之间传输数据的最快解决方案.

我还测试了HDF5,发现它比np.load()和np.save()要慢,所以如果你的平台上有足够的DDR内存,请使用np.save()和np.load().

  • 例如,尝试使用此 /sf/answers/3429854921/ 进行压缩和不进行压缩(压缩限制约为 500-800 MB/s。对于可良好压缩的数据,您可以在 HDF 5 上获得更多吞吐量HDD 甚至是 SATA3 SSD。但主要优点是以顺序 IO 速度沿任意轴读取或写入阵列部分。如果 IO 速度确实很重要,那么阵列也可能比 RAM 更大...... (2认同)
  • @Duane不,这是不可能的,如果你想从一个非常大的数字随机访问一小部分数据,我们唯一的选择是数据库,HDF5或其他一些可以支持我们随机访问硬盘的机制.我建议只有当我们有足够的DDR内存空间并且我们的数据不是那么大时才使用np.load(),至少我们的数据可以放入我们的内存空间. (2认同)

oma*_*oud 5

我创建了一个基准测试工具,并使用 python 3.10 生成了各种加载/保存方法的基准测试。我在快速 NVMe 上运行它(传输速率 >6GB/s,因此此处的测量不受磁盘 I/O 限制)。测试的 numpy 数组的大小从小到 32GB 不等。结果可以在这里看到。该工具的 github 存储库位于此处

结果各不相同,并且受数组大小的影响;有些方法会执行数据压缩,因此需要对此进行权衡。以下是 I/O 速率的一个概念(更多结果请参见上面的链接):

IO速率

图例(用于保存): np: np.save()、 npz: np.savez()、 npzc: np.savez_compressed()、 hdf5: h5py.File().create_dataset()、 pickle: pickle.dump()、 zarr_zip: zarr.save_array()w/.zip扩展、 zarr_zip: zarr.save_array()w/.zarr扩展、 pytables: tables.open_file().create_array()、 fast_np: 使用答案。