加载速度 vs 内存:如何高效地从 h5 文件加载大型数组
Loading speed vs memory: how to efficiently load large arrays from h5 file
我一直面临以下问题:我必须遍历 num_objects = 897
个对象,对于每个对象我都必须使用 num_files = 2120
h5 文件。这些文件非常大,每个都是 1.48 GB,我感兴趣的内容是每个文件中包含的 3 个大小为 256 x 256 x 256 的浮点数数组(v1
、v2
和 v3
).也就是说,循环看起来像:
for i in range(num_objects):
...
for j in range(num_files):
some operation with the three 256 x 256 x 256 arrays in each file
我目前加载它们的方式是在最内层循环中执行以下操作:
f = h5py.File('output_'+str(q)+'.h5','r')
key1 = np.array(f['key1'])
v1=key1[:,:,:,0]
v2=key2[:,:,:,1]
v3=key3[:,:,:,2]
上面每次为每个对象加载文件的选项显然很慢。另一方面,一次加载所有文件并将它们导入字典会导致过度使用内存并且我的工作被终止。一些诊断:
- 上述方法每个文件、每个对象需要 0.48 秒,因此总共花费 10.5 天 (!) 仅用于此操作。
- 我尝试将
key1
导出到 npz 文件,但实际上每个文件要慢 0.7 秒。
- 我将每个文件的
v1
、v2
和 v3
分别导出到 npz 文件(即每个 h5 文件有 3 个 npz 文件),但这只节省了我 1.5 天的时间总计
有没有人有其他的idea/suggestion我可以尝试速度快,同时不受过多内存使用的限制?
据我了解,您有 2120 个 .h5 文件。您是否只为每个文件读取数据集 f['key1']
中的 3 个数组? (或者是否有其他数据集?)如果您 only/always 阅读 f['key1']
,这是您无法通过编程解决的瓶颈。使用 SSD 会有所帮助(因为 I/O 比 HDD 快)。否则,您将不得不重新组织数据。您系统上的 RAM 容量将决定您可以同时读取的阵列数量。你有多少内存?
您可能会通过小的代码更改获得一点速度。 v1=key1[:,:,:,0]
returns v1 作为数组(v2 和 v3 相同)。无需将数据集 f['key1']
读入数组。这样做会使您的内存占用量加倍。 (顺便说一句,是否有理由将您的数组转换为字典?)
下面的过程仅通过从 h5py f['key1']
对象中切片 v1,v2,v3
创建了 3 个数组。它会将每个循环的内存占用减少 50%。
f = h5py.File('output_'+str(q)+'.h5','r')
key1 = f['key1']
## key1 is returned as a h5py dataset OBJECT, not an array
v1=key1[:,:,:,0]
v2=key2[:,:,:,1]
v3=key3[:,:,:,2]
在 HDF5 方面,由于您总是切出最后一个轴,因此您的块参数可能会改进 I/O。但是,如果您想更改块形状,则必须重新创建 .h5 文件。所以,这可能不会节省时间(至少在 short-term)。
同意@kcw78,根据我的经验,瓶颈通常是你加载的数据比你需要的多。因此,除非你需要,否则不要将数据集转换为数组,并且只切分你需要的部分(你需要整个 [:,:,0]
还是只需要它的一个子集?)。
此外,如果可以,请更改循环的顺序,以便每个文件只打开一次。
for j in range(num_files):
...
for i in range(num_objects):
some operation with the three 256 x 256 x 256 arrays in each file
另一种方法是使用外部工具(如h5copy)在更小的数据集中提取您需要的数据,然后在python中读取它,以避免python 的开销(老实说,这可能不是那么多)。
最后,您可以使用多处理来利用 CPU 个核心,只要您的任务彼此相对独立即可。你有一个例子 here.
h5py 是数据表示 class 支持大部分 NumPy 数据类型,支持切片等传统 NumPy 操作,以及形状和大小属性等各种描述性属性。
有一个很酷的选项,您可以使用启用了 chunked 关键字的 create_dataset 方法以块的形式表示数据集:
dset = f.create_dataset("chunked", (1000, 1000), chunks=(100, 100))
这样做的好处是您可以轻松调整数据集的大小,现在就您的情况而言,您只需读取一个块,而不是整个 1.4 GB 的数据。
但要注意分块的影响:不同的数据大小最适合不同的分块大小。有一个 autochunk 选项可以自动选择这个块大小,而不是通过命中和试验:
dset = f.create_dataset("autochunk", (1000, 1000), chunks=True)
干杯
我一直面临以下问题:我必须遍历 num_objects = 897
个对象,对于每个对象我都必须使用 num_files = 2120
h5 文件。这些文件非常大,每个都是 1.48 GB,我感兴趣的内容是每个文件中包含的 3 个大小为 256 x 256 x 256 的浮点数数组(v1
、v2
和 v3
).也就是说,循环看起来像:
for i in range(num_objects):
...
for j in range(num_files):
some operation with the three 256 x 256 x 256 arrays in each file
我目前加载它们的方式是在最内层循环中执行以下操作:
f = h5py.File('output_'+str(q)+'.h5','r')
key1 = np.array(f['key1'])
v1=key1[:,:,:,0]
v2=key2[:,:,:,1]
v3=key3[:,:,:,2]
上面每次为每个对象加载文件的选项显然很慢。另一方面,一次加载所有文件并将它们导入字典会导致过度使用内存并且我的工作被终止。一些诊断:
- 上述方法每个文件、每个对象需要 0.48 秒,因此总共花费 10.5 天 (!) 仅用于此操作。
- 我尝试将
key1
导出到 npz 文件,但实际上每个文件要慢 0.7 秒。 - 我将每个文件的
v1
、v2
和v3
分别导出到 npz 文件(即每个 h5 文件有 3 个 npz 文件),但这只节省了我 1.5 天的时间总计
有没有人有其他的idea/suggestion我可以尝试速度快,同时不受过多内存使用的限制?
据我了解,您有 2120 个 .h5 文件。您是否只为每个文件读取数据集 f['key1']
中的 3 个数组? (或者是否有其他数据集?)如果您 only/always 阅读 f['key1']
,这是您无法通过编程解决的瓶颈。使用 SSD 会有所帮助(因为 I/O 比 HDD 快)。否则,您将不得不重新组织数据。您系统上的 RAM 容量将决定您可以同时读取的阵列数量。你有多少内存?
您可能会通过小的代码更改获得一点速度。 v1=key1[:,:,:,0]
returns v1 作为数组(v2 和 v3 相同)。无需将数据集 f['key1']
读入数组。这样做会使您的内存占用量加倍。 (顺便说一句,是否有理由将您的数组转换为字典?)
下面的过程仅通过从 h5py f['key1']
对象中切片 v1,v2,v3
创建了 3 个数组。它会将每个循环的内存占用减少 50%。
f = h5py.File('output_'+str(q)+'.h5','r')
key1 = f['key1']
## key1 is returned as a h5py dataset OBJECT, not an array
v1=key1[:,:,:,0]
v2=key2[:,:,:,1]
v3=key3[:,:,:,2]
在 HDF5 方面,由于您总是切出最后一个轴,因此您的块参数可能会改进 I/O。但是,如果您想更改块形状,则必须重新创建 .h5 文件。所以,这可能不会节省时间(至少在 short-term)。
同意@kcw78,根据我的经验,瓶颈通常是你加载的数据比你需要的多。因此,除非你需要,否则不要将数据集转换为数组,并且只切分你需要的部分(你需要整个 [:,:,0]
还是只需要它的一个子集?)。
此外,如果可以,请更改循环的顺序,以便每个文件只打开一次。
for j in range(num_files):
...
for i in range(num_objects):
some operation with the three 256 x 256 x 256 arrays in each file
另一种方法是使用外部工具(如h5copy)在更小的数据集中提取您需要的数据,然后在python中读取它,以避免python 的开销(老实说,这可能不是那么多)。
最后,您可以使用多处理来利用 CPU 个核心,只要您的任务彼此相对独立即可。你有一个例子 here.
h5py 是数据表示 class 支持大部分 NumPy 数据类型,支持切片等传统 NumPy 操作,以及形状和大小属性等各种描述性属性。
有一个很酷的选项,您可以使用启用了 chunked 关键字的 create_dataset 方法以块的形式表示数据集:
dset = f.create_dataset("chunked", (1000, 1000), chunks=(100, 100))
这样做的好处是您可以轻松调整数据集的大小,现在就您的情况而言,您只需读取一个块,而不是整个 1.4 GB 的数据。
但要注意分块的影响:不同的数据大小最适合不同的分块大小。有一个 autochunk 选项可以自动选择这个块大小,而不是通过命中和试验:
dset = f.create_dataset("autochunk", (1000, 1000), chunks=True)
干杯