如何使用 h5py 优化顺序写入以提高之后读取文件时的速度?

How to optimize sequential writes with h5py to increase speed when reading the file afterwards?

我处理了一些输入数据,如果我一次完成所有这些,就会给我一个 float32 和典型形状 (5000, 30000000) 的数据集。 (第 0 个轴的长度是固定的,第 1 个轴的长度是变化的,但我在开始之前就知道它会是什么)。

因为那是 ~600GB 并且不适合内存,所以我必须沿第一个轴切割它并以 (5000, blocksize) 的块处理它。我不能沿第 0 轴切割它,并且由于 RAM 限制 blocksize 通常在 40000 左右。目前我正在将每个块顺序写入 hdf5 数据集,创建数据集如下:

fout = h5py.File(fname, "a")

blocksize = 40000

block_to_write = np.random.random((5000, blocksize))
fout.create_dataset("data", data=block_to_write, maxshape=(5000, None))

然后遍历块并通过

添加到它
fout["data"].resize((fout["data"].shape[1] + blocksize), axis=1)
fout["data"][:, -blocksize:] = block_to_write

这可以在可接受的时间内运行。

我需要输入下一步的最终产品是输出的每一行的二进制文件。这是别人的软件,所以不幸的是我在那里没有灵活性。

问题是像

这样一排读
fin = h5py.File(fname, 'r')
data = fin['data']
a = data[0,:]

5000 行需要大约 4 分钟,太长了!

有什么方法可以改变我的写法,使我的读法更快?或者我还能做些什么吗?

我应该让每一行在 hdf5 文件中都有自己的数据集吗?我认为进行大量单独写入会太慢,但也许更好?

我尝试直接写入二进制文件 - 在循环外打开它们,在循环期间写入它们,然后在之后关闭它们 - 但我 运行 进入 OSError: [Errno 24] Too many open files。我还没有尝试过,但我认为打开文件并在循环内关闭它们会使速度太慢。

您的问题类似于我最近回答的一个 SO/h5py 问题:h5py extremely slow writing。显然您正在获得可接受的写入性能,并希望提高读取性能。

影响 h5py I/O 性能的 2 个最重要的因素是:1) 块 size/shape,以及 2) I/O 数据块的大小。 h5py 文档建议将块大小保持在 10 KB 到 1 MB 之间——更大的数据集更大。参考:h5py Chunked Storage. I have also found write performance degrades when I/O data blocks are "too small". Ref: pytables writes much faster than h5py。你读数据块的大小肯定够大了。

因此,我最初的直觉是调查块大小对 I/O 性能的影响。设置最佳块大小是一门艺术。调整该值的最佳方法是启用分块,让 h5py 定义默认大小,然后查看您是否获得可接受的性能。您没有定义 chunks 参数。但是,因为您定义了 maxshape 参数,所以会自动启用默认大小的分块(基于数据集的初始大小)。 (如果没有分块,I/O 在这种大小的文件上会非常慢。)对您的问题的额外考虑:最佳块大小必须平衡写入数据块的大小(5000 x 40_000 ) 与读取数据块 (1 x 30_000_000).

我参数化了你的代码,这样我就可以修改尺寸了。当我这样做的时候,我发现了一些有趣的事情。当我 运行 创建文件后将其作为一个单独的进程时,读取数据的速度要快得多。而且,默认的块大小似乎可以提供足够的读取性能。 (最初我打算对不同的块大小值进行基准测试。)

注意:我只创建了一个 78GB 的​​文件(4_000_000 列)。在我的 Windows 系统上,这需要 >13 分钟才能达到 运行。我不想等待 90 分钟来创建一个 600GB 的文件。如果要测试 30_000_000 列,可以修改 n_blocks=750。 :-) 此 post.

末尾的所有代码

接下来我创建了一个单独的程序来读取数据。默认块大小的读取性能很快:(40, 625)。计时输出如下:

Time to read first row: 0.28 (in sec)
Time to read last row:  0.28

有趣的是,我每次测试的阅读时间都不一样。上面的值非常一致,但偶尔我会得到 7-10 秒的阅读时间。不知道为什么会这样。

I 运行 3 次测试(在所有情况下 block_to_write.shape=(500,40_000)):

  1. 默认chunksize=(40,625) [95KB];对于 500x40_000 数据集(调整大小)
  2. 默认chunksize=(10,15625) [596KB];对于 500x4_000_000 数据集(未调整大小)
  3. 用户自定义chunksize=(10,40_000)[1.526MB];对于 500x4_000_000 数据集(未调整大小)

更大的块提高了读取性能,但默认值的速度非常快。 (块大小对写入性能的影响非常小。)以下所有 3 个的输出。

dataset chunkshape: (40, 625)
Time to read first row: 0.28
Time to read last row: 0.28

dataset chunkshape: (10, 15625)
Time to read first row: 0.05
Time to read last row: 0.06

dataset chunkshape: (10, 40000)
Time to read first row: 0.00
Time to read last row: 0.02

创建我的测试文件的代码如下:

with h5py.File(fname, 'w') as fout:
    blocksize = 40_000
    n_blocks = 100
    n_rows = 5_000
    block_to_write = np.random.random((n_rows, blocksize))
    start = time.time()
    for cnt in range(n_blocks):
        incr = time.time()
        print(f'Working on loop: {cnt}', end='')
        if "data" not in fout:
            fout.create_dataset("data", shape=(n_rows,blocksize), 
                        maxshape=(n_rows, None)) #, chunks=(10,blocksize))            
        else:    
            fout["data"].resize((fout["data"].shape[1] + blocksize), axis=1)
        
        fout["data"][:, cnt*blocksize:(cnt+1)*blocksize] = block_to_write
        print(f' - Time to add block: {time.time()-incr:.2f}')
print(f'Done creating file: {fname}')
print(f'Time to create {n_blocks}x{blocksize:,} columns: {time.time()-start:.2f}\n')

从下面的测试文件中读取 2 个不同数组的代码:

with h5py.File(fname, 'r') as fin:
    print(f'dataset shape: {fin["data"].shape}')
    print(f'dataset chunkshape: {fin["data"].chunks}')
    start = time.time()
    data = fin["data"][0,:]
    print(f'Time to read first row: {time.time()-start:.2f}')
    start = time.time()
    data = fin["data"][-1,:]
    print(f'Time to read last row: {time.time()-start:.2f}'