为啥读取整个 hdf5 数据集比读取切片更快

Posted

技术标签:

【中文标题】为啥读取整个 hdf5 数据集比读取切片更快【英文标题】:Why is it faster to read whole hdf5 dataset than a slice为什么读取整个 hdf5 数据集比读取切片更快 【发布时间】:2019-04-26 18:28:49 【问题描述】:

我正在试图弄清楚为什么会发生这种情况:

In [1]: import time, h5py as h5
In [2]: f = h5.File('myfile.hdf5', 'r')                                                                                                                                    
In [3]: st = time.time(); data = f["data"].value[0,:,1,...]; elapsed = time.time() - st;
In [4]: elapsed
Out[4]: 11.127676010131836
In [5]: st = time.time(); data = f["data"][0,:,1,...]; elapsed2 = time.time() - st;
In [6]: elapsed2
Out[6]: 59.810582399368286
In [7]: f["data"].shape
Out[7]: (1, 4096, 6, 16, 16, 16, 16)
In [8]: f["data"].chunks
Out[8]: (1, 4096, 1, 16, 16, 16, 16)

如您所见,将整个数据集加载到内存中然后取出切片比从数据集中取出相同切片要快。

块大小与切片匹配,所以它应该都是连续内存,对吧?那为什么这么慢呢?

数据集使用 gzip (opts=2) 压缩。

根据 Andrew 的评论,我运行它以清除两次读取之间的缓存:

elapsed1: 11.001180410385132
elapsed2: 43.19723725318909
48.61user 4.45system 0:54.65elapsed 97%CPU (0avgtext+0avgdata 8431596maxresident)k
479584inputs+0outputs (106major+3764414minor)pagefaults 0swaps

(下一次运行在两次读取之间有 10 秒的延迟以清除缓存)

elapsed1: 11.46790862083435
elapsed2: 43.438515186309814

48.54user 4.66system 1:05.71elapsed 80%CPU (0avgtext+0avgdata 8431944maxresident)k
732504inputs+0outputs (220major+3764449minor)pagefaults 0swaps

【问题讨论】:

你的操作系统是什么?如果它在 Linux 上,请尝试在 /usr/bin/time /your/python/here 下运行不同版本的 Python 代码。这将显示 CPU 时间花在哪里 - 内核/系统或用户空间,并提供一些关于正在发生的事情的线索。同样在 Linux 上,您可以使用 strace 查看两个版本中进行了哪些系统调用。我怀疑“将其全部读入内存”仅将数据解压缩一次,而较慢的方法必须多次查找和解压缩。 另外,如果您使用的是 LInux,请在运行每个不同版本之前flush your page cache 您正在使用花式切片。 ***.com/a/48405220/4045774我在“最简单的花式索引形式”中提到了这个案例。你的块也非常大。如果您使用适当的块缓存大小,则这不是必需的。 还有比 gzip 更快的压缩算法。如果多种语言之间的可移植性(gzip 与每个 HDF5 安装一起提供,不像更快的 blosc)不是问题,请考虑 blosc。如果您的数据提供高压缩比并存储在 HDD 上,它通常比未压缩的数据集更快。例如。来自 HDD 的 900MB/s -> ***.com/a/48997927/4045774 不知道data[0][:,1]是怎么做到的。 【参考方案1】:

首先我进行了自己的测试。我没有你的 HDF5 文件,所以使用我的一个测试文件。我的测试表数据集有大约 54,000 行(看起来比你的大)。 .value[] 的计时结果给出了

>>> elapsed
0.15540122985839844

使用 NumPy 索引的计时结果给出:

>>> elapsed2
0.12980079650878906

所以,我看不出性能上有太大差异。也许这与我们正在测试的数据集大小或数据表的复杂性有关?

稍微阅读一下最新的 h5py 文档,就有一些关于 Dataset.value 的有趣 cmets(来自 2.8.0 版 - 2018 年 6 月 5 日;重点是我的):Dataset.value 属性现已弃用. 可追溯到 h5py 1.0 的属性 Dataset.value 已被弃用,并将在以后的版本中删除。 此属性将整个数据集转储到 NumPy 数组中。 使用 .value 的代码应更新为使用 NumPy 索引,并酌情使用 mydataset[...]mydataset[()]

您的计时测试似乎与上面突出显示的观察结果相反。

我认为您需要请 h5py 开发人员评论性能差异(以及数据的存储位置——内存与磁盘)。您是否与h5py user group 核对过?

编辑: 发布后,我发现了这个 SO Q&A。它有很多优秀的 cmets,并包含来自 h5py 开发人员的回复:h5py: Correct way to slice array datasets

【讨论】:

OP 的矩阵有超过 10 亿个元素。

以上是关于为啥读取整个 hdf5 数据集比读取切片更快的主要内容,如果未能解决你的问题,请参考以下文章

如何从 C++ 中的 hdf5 文件中读取数据块?

使用 pandas 读取 hdf5 数据集

在 MATLAB 中读取 HDF5 数据集的一些特定元素

HDF5 C# pinvoke 读取数据集名称列表

为啥 Monk 的问题的测试集比他们的训练集大?

读取存储在 HDF5 中的部分数据集 - python 2.7