用于读取行的最佳HDF5数据集块形状

时间:2018-01-22 15:41:28

标签: python performance dataset hdf5 h5py

我有一个合理的大小(18GB压缩)HDF5数据集,我希望优化读取行的速度。形状是(639038,10000)。我将在数据集中多次读取选择的行(比如说〜1000行)。所以我不能用x:(x + 1000)来切片行。

使用h5py从内存不足的HDF5中读取行已经很慢了,因为我必须传递一个已排序的列表并采用花式索引。有没有办法避免花哨的索引,或者我可以使用更好的块​​形状/大小?

我已经阅读了经验法则,例如1MB-10MB大小的大小,并选择与我正在阅读的形状一致的形状。但是,构建大量具有不同块形状的HDF5文件进行测试的计算成本非常高且非常慢。

对于每行约1,000行的选择,我立即将它们相加以获得长度为10,000的数组。我当前的数据集如下所示:

'10000': {'chunks': (64, 1000),
          'compression': 'lzf',
          'compression_opts': None,
          'dtype': dtype('float32'),
          'fillvalue': 0.0,
          'maxshape': (None, 10000),
          'shape': (639038, 10000),
          'shuffle': False,
          'size': 2095412704}

我已经尝试过了:

  • 使用块形状(128,10000)重写数据集(我计算为~5MB),速度非常慢。
  • 我看了dask.array进行优化,但由于~1,000行容易在内存中使用,所以我认为没有任何好处。

1 个答案:

答案 0 :(得分:5)

查找正确的块缓存大小

起初我不想讨论一些一般事情。 知道每个单独的块只能作为一个整体进行读取或写入是非常重要的。 h5py的标准块高速缓存大小可以避免过多的磁盘I / O,每个默认值只有1 MB,并且在很多情况下应该增加,这将在后面讨论。

举个例子:

  • 我们有一个dset的形状(639038,10000),float32(25,5 GB未压缩)
  • 我们不想明确地编写我们的数据列dset[:,i]=arr并按行明智地阅读arr=dset[i,:]
  • 我们为这类工作选择一个完全错误的块状,即(1,10000)

在这种情况下,读取速度不会很差(尽管块大小有点小)因为我们只读取我们正在使用的数据。但是当我们写这个数据集时会发生什么?如果我们访问一列,则写入每个块的一个浮点数。这意味着我们实际上每次迭代都会写入整个数据集(25,5 GB)并每隔一段时间读取整个数据集。这是因为如果你修改了一个块,你必须先读取它,如果它没有被缓存(我认为这里的块高速缓存大小低于25.5 GB)。

那么我们可以在这里改进什么? 在这种情况下,我们必须在写/读速度和块缓存使用的内存之间做出妥协。

一种能够提供正常/读写速度的假设:

  • 我们选择(100,1000)
  • 的大小
  • 如果我们不想迭代第一个维度,我们至少需要(1000 * 639038 * 4 - > 2.55 GB)缓存以避免额外的IO开销,如上所述(100 * 10000 * 4 - > 0,4 MB)。
  • 因此,我们应该在此示例中提供至少2,6 GB的块数据缓存。使用h5py-cache https://pypi.python.org/pypi/h5py-cache/1.0
  • 可以轻松完成此操作

<强>结论 通常没有正确的块大小或形状,它在很大程度上取决于使用哪个任务。如果不仔细考虑块缓存,就不要选择块大小或形状。在随机读/写方面,RAM的速度比最快的SSD快。

关于您的问题 我只是阅读随机行,不正确的chunk-cache-size是你真正的问题。

将以下代码的性能与您的版本进行比较:

import h5py as h5
import time
import numpy as np
import h5py_cache as h5c

def ReadingAndWriting():
    File_Name_HDF5='Test.h5'

    shape = (639038, 10000)
    chunk_shape=(100, 1000)
    Array=np.array(np.random.rand(shape[0]),np.float32)

    #We are using 4GB of chunk_cache_mem here
    f = h5c.File(File_Name_HDF5, 'w',chunk_cache_mem_size=1024**2*4000)
    d = f.create_dataset('Test', shape ,dtype='f',chunks=chunk_shape,compression="lzf")

    #Writing columns
    t1=time.time()
    for i in xrange(0,shape[1]):
        d[:,i:i+1]=np.expand_dims(Array, 1)

    f.close()
    print(time.time()-t1)

    # Reading random rows
    # If we read one row there are actually 100 read, but if we access a row
    # which is already in cache we would see a huge speed up.
    f = h5c.File(File_Name_HDF5,'r',chunk_cache_mem_size=1024**2*4000)
    d = f["Test"]
    for j in xrange(0,639):
        t1=time.time()
        # With more iterations it will be more likely that we hit a already cached row
        inds=np.random.randint(0, high=shape[0]-1, size=1000)
        for i in xrange(0,inds.shape[0]):
            Array=np.copy(d[inds[i],:])
        print(time.time()-t1)
    f.close()


if __name__ == "__main__":
    ReadingAndWriting()

最简单的花式切片形式

我在评论中写道,在最近的版本中我无法看到这种行为。我错了。比较以下内容:

import h5py as h5
import time
import numpy as np
import h5py_cache as h5c

def Writing():
    File_Name_HDF5='Test.h5'

    shape = (63903, 10000)
    Array=np.array(np.random.rand(shape[0]),np.float32)

    # Writing_1 normal indexing
    f = h5c.File(File_Name_HDF5, 'w',chunk_cache_mem_size=1024**3)
    d = f.create_dataset('Test', shape ,dtype='f',chunks=(10000,shape[1]/50))
    t1=time.time()
    for i in xrange(0,shape[1]):
        d[:,i:i+1]=np.expand_dims(Array,1)

    f.close()
    print(time.time()-t1)

    # Writing_2 simplest form of fancy indexing 
    f = h5c.File(File_Name_HDF5, 'w',chunk_cache_mem_size=1024**3)
    d = f.create_dataset('Test', shape ,dtype='f',chunks=(10000,shape[1]/50))
    t1=time.time()
    for i in xrange(0,shape[1]):
        d[:,i]=Array

    f.close()
    print(time.time()-t1)


if __name__ == "__main__":
    Writing()

这为我的SSD提供了第一版的10,8秒和第二版的55秒。