保存到 hdf5 非常慢(Python 冻结)

Posted

技术标签:

【中文标题】保存到 hdf5 非常慢(Python 冻结)【英文标题】:Saving to hdf5 is very slow (Python freezing) 【发布时间】:2018-07-18 05:37:51 【问题描述】:

我正在尝试将瓶颈值保存到新创建的 hdf5 文件中。 瓶颈值以(120,10,10, 2048) 的形式出现。 保存一个单独的批次占用了超过 16 个演出,python 似乎在这一批次中冻结了。根据最近的发现(见更新,看来hdf5占用大内存还可以,但冻结部分似乎是个小故障。

我只是想保存前 2 个批次用于测试目的,而且只有 训练数据集(再次,这是一个测试运行),但我什至无法通过第一批。它只是停在第一批,不会循环到下一次迭代。如果我尝试检查 hdf5,资源管理器会变得迟缓,Python 会冻结。如果我试图杀死 Python(即使没有检查 hdf5 文件),Python 也不会正确关闭并强制重启。

以下是相关代码和数据:

总数据点约为 90,000 个,分批发布 120 个。

Bottleneck shape is (120,10,10,2048)

所以我要保存的第一批是(120,10,10,2048)

这是我尝试保存数据集的方法:

with h5py.File(hdf5_path, mode='w') as hdf5:
                hdf5.create_dataset("train_bottle", train_shape, np.float32)
                hdf5.create_dataset("train_labels", (len(train.filenames), params['bottle_labels']),np.uint8)
                hdf5.create_dataset("validation_bottle", validation_shape, np.float32)
                hdf5.create_dataset("validation_labels",
                                              (len(valid.filenames),params['bottle_labels']),np.uint8)



 #this first part above works fine

                current_iteration = 0
                print('created_datasets')
                for x, y in train:

                    number_of_examples = len(train.filenames) # number of images
                    prediction = model.predict(x)
                    labels = y
                    print(prediction.shape) # (120,10,10,2048)
                    print(y.shape) # (120, 12)
                    print('start',current_iteration*params['batch_size']) # 0
                    print('end',(current_iteration+1) * params['batch_size']) # 120

                    hdf5["train_bottle"][current_iteration*params['batch_size']: (current_iteration+1) * params['batch_size'],...] = prediction
                    hdf5["train_labels"][current_iteration*params['batch_size']: (current_iteration+1) * params['batch_size'],...] = labels
                    current_iteration += 1
                    print(current_iteration)
                    if current_iteration == 3:
                       break

这是打印语句的输出:

(90827, 10, 10, 2048) # print(train_shape)

(6831, 10, 10, 2048)  # print(validation_shape)
created_datasets
(120, 10, 10, 2048)  # print(prediction.shape)
(120, 12)           #label.shape
start 0             #start of batch
end 120             #end of batch

# Just stalls here instead of printing `print(current_iteration)`

它只是在这里停顿一段时间(20 分钟 +),hdf5 文件的大小慢慢增长(现在大约 20 个演出,在我强行杀死之前)。实际上我什至不能用任务管理器强制杀死,我必须重新启动操作系统,在这种情况下才能真正杀死 Python。

更新

在玩了一会儿我的代码之后,似乎有一个奇怪的错误/行为。

相关部分在这里:

          hdf5["train_bottle"][current_iteration*params['batch_size']: (current_iteration+1) * params['batch_size'],...] = prediction
                hdf5["train_labels"][current_iteration*params['batch_size']: (current_iteration+1) * params['batch_size'],...] = labels

如果我运行这些行中的任何一个,我的脚本将通过迭代,并按预期自动中断。因此,如果我运行非此即彼,则不会冻结。它也发生得相当快——不到一分钟。

如果我运行第一行 ('train_bottle'),我的内存占用了大约 69-72 gigs,即使它只是几批。如果我尝试更多批次,内存是一样的。所以我假设train_bottle 决定存储基于我分配数据集的大小参数,而不是实际填充时。 因此,尽管有 72 个演出,但它的运行速度相当快(一分钟)。

如果我运行第二行 train_labels ,我的内存会占用几兆字节。 迭代没有问题,执行break语句。

但是,现在出现了问题,如果我尝试运行两条线(在我的情况下这是必要的,因为我需要同时保存“train_bottle”和“train_labels”),我在第一次迭代时遇到了冻结,并且它不会继续到第二次迭代,即使在 20 分钟后也是如此。 Hdf5 文件正在缓慢增长,但如果我尝试访问它,Windows 资源管理器会变慢,而且我无法关闭 Python——我必须重新启动操作系统。

所以我不确定在尝试运行两条线路时出现什么问题——就好像我运行了内存饥饿的train_data 线路,如果运行良好并在一分钟内结束。

【问题讨论】:

我不知道16GB 的估计值来自哪里,但我认为这是一个错误的假设。单个批次需要120 * 10 * 10 * 2048 * 4 bytes* what is approximately 94MB. So a full dataset which you want to save has 94 * 90000 MB` 大约等于9TB。这就是您的错误的来源。 感谢您的回复。它实际上总共有 90000 张图像,所以批次将是 (90000/120) = 750 * 94 MB。哪个应该 7.5 演出?但是,我只是想保存前两批,应该是 94 * 2。至于估计,我实际上每 30 秒左右手动检查一次文件,我一直看到它增加了这些演出。我无法弄清楚我的代码中是否存在导致此问题的错误。我正在使用外部硬盘驱动器,想知道这是否会导致问题(太慢?)。我的内部硬盘快满了,我必须找到要删除的东西来测试它。 老兄-750 * 94MB = 69 GB,不是6.9GB:D 多久后达到20GB? @MarcinMożejko 啊,你是对的,但我只是想保存前两到三批。而且它甚至没有通过第一批并积累了大约 20 个演出。奇怪的是,如果我省略 ['train_bottle'] line and just run the ['train_labels']` 行,它将通过前几批并按预期中断(也很快)。 【参考方案1】:

这个答案更像是对@max9111 和@Clock ZHONG 之间争论的评论。我写这篇文章是为了帮助其他人想知道哪个更快 HDF5 或 np.save()。

我使用了@max9111 提供的代码,并按照@Clock ZHONG 的建议进行了修改。确切的 jupyter notebook 可以在https://github.com/wornbb/save_speed_test找到。

简而言之,按照我的规范:

SSD:三星 960 EVO CPU:i7-7700K 内存:2133 MHz 16GB 操作系统:Win 10

HDF5 达到 1339.5 MB/s,而 np.save 仅为 924.9 MB/s(未压缩)。

另外,正如@Clock ZHONG 所指出的,他/她在使用 lzf -Filter 时遇到了问题。如果你也有这个问题,可以在 win 10 上使用 python3 的 conda 分发包运行已发布的 jupyter notebook。

【讨论】:

仅保存/加载大型数组的最佳方式取决于各种因素(最重要的可实现压缩比)。在许多情况下,它有可能大大优于 HDF5(仅单线程压缩过滤器)。例如。 ***.com/a/56761075/4045774 吞吐量也会有很大差异。使用哪种 SSD?它是满的还是空的?数组有多大? (许多 SSD 都有快速 SLC 缓存)...【参考方案2】:

如果你有足够的 DDR 内存并且想要极快的数据加载和保存性能,请直接使用 np.load()&np.save()。 https://***.com/a/49046312/2018567 np.load()&np.save() 可以为您提供最快的数据加载和保存性能,到目前为止,我找不到任何其他工具或框架可以与之匹敌,甚至 HDF5 的性能只有 1/5 ~ 1/7它。

【讨论】:

请注意,要优于上述解决方案的唯一方法是使用 PCIe SSD。即使这样,您也必须将紧凑的数据集与 np.save& 和 np.load 进行比较以保持一致。例如。 f = h5.File(hdf5_path, 'w') f.create_dataset("my_dataset",data=numpy_array) f.close() 在这个例子中,我得到了几乎没有 CPU 的 SATA3 SSD(大约 MB/s)的完整带宽用法。但在大多数情况下,这是不推荐的,因为您几乎失去了 HDF5 的所有优势(仅写入或读取文件的一部分、压缩) max9111,我们不必争论 np.load()&np.save() 或 HDF5 哪个更快,您只需将 HDF5 函数替换为 np.save() 即可。我的测试结果显示它的带宽为 2.3GBps(18Gbps)。是HDF5性能的8倍以上。我相信你的电脑比我的要快得多,所以可能是 4~5GBps。请尝试一下,只需将 dset_train_bottle() 替换为 np.save()。让我们知道您的测试结果。这不是一项很大的工作量。【参考方案3】:

将数据写入 HDF5

如果您在未指定 chunkshape 的情况下写入分块数据集,h5py 会自动为您执行此操作。由于 h5py 不知道您如何不从数据集中写入或读取数据,因此这通常会导致性能不佳。

您还使用 1 MB 的默认块缓存大小。如果您只写入块的一部分并且该块不适合缓存(这很可能是 1MP 块缓存大小),则整个块将在内存中读取、修改并写回磁盘。如果这种情况多次发生,您将看到远远超出 HDD/SSD 的顺序 IO 速度的性能。

在下面的示例中,我假设您只在第一个维度上读取或写入。如果不是,则必须根据您的需要进行修改。

import numpy as np
import tables #register blosc
import h5py as h5
import h5py_cache as h5c
import time

batch_size=120
train_shape=(90827, 10, 10, 2048)
hdf5_path='Test.h5'
# As we are writing whole chunks here this isn't realy needed,
# if you forget to set a large enough chunk-cache-size when not writing or reading 
# whole chunks, the performance will be extremely bad. (chunks can only be read or written as a whole)
f = h5c.File(hdf5_path, 'w',chunk_cache_mem_size=1024**2*200) #200 MB cache size
dset_train_bottle = f.create_dataset("train_bottle", shape=train_shape,dtype=np.float32,chunks=(10, 10, 10, 2048),compression=32001,compression_opts=(0, 0, 0, 0, 9, 1, 1), shuffle=False)
prediction=np.array(np.arange(120*10*10*2048),np.float32).reshape(120,10,10,2048)
t1=time.time()
#Testing with 2GB of data
for i in range(20):
    #prediction=np.array(np.arange(120*10*10*2048),np.float32).reshape(120,10,10,2048)
    dset_train_bottle[i*batch_size:(i+1)*batch_size,:,:,:]=prediction

f.close()
print(time.time()-t1)
print("MB/s: " + str(2000/(time.time()-t1)))

编辑 循环中的数据创建花费了相当多的时间,所以我在时间测量之前创建了数据。

这应该提供至少 900 MB/s 的吞吐量(CPU 受限)。使用真实数据和较低的压缩率,您应该可以轻松达到硬盘的顺序 IO 速度。

使用 with 语句打开一个 HDF5-File 也可能导致性能不佳,如果您错误地多次调用此块。这将关闭并重新打开文件,删除块缓存。

为了确定正确的块大小,我还建议: https://***.com/a/48405220/4045774 https://***.com/a/44961222/4045774

【讨论】:

它似乎正在工作。我需要再进行一些测试以确保(希望在明天之前)非常感谢您的详细帖子。这是我第一次阅读有关分块数据集的内容。我找到了这个链接,它解释了chunks 是什么:support.hdfgroup.org/HDF5/doc/_topic/Chunking 我会在处理完一些事情后尝试阅读它。如果您有任何其他推荐的链接,我将不胜感激,因为分块是我不太熟悉的东西。 谢谢。它在运行几个不同的测试后工作。 您的存储设备是否达到顺序 IO 速度?如果不是,则解决方案不是最佳的。 我将不得不再次通过 time.time 在虚拟装置上进行测试,但对于 4 场演出来说它非常快。我必须检查我的外部存储的规格,但我认为它是 7200 rpm,所以 80-160 MB/s 应该是标准?你觉得我可以达到 500 MB/s 左右? 请注意,这不是您能得到的最好的。压缩过滤器只是单线程的,也许 pytables 和 blosc 过滤器没有使用启用的 avx2 编译。 HDF5-Filter Pipeline 也可能有点慢。有关可实现的压缩和解压缩速度,请查看github.com/Blosc/python-blosc

以上是关于保存到 hdf5 非常慢(Python 冻结)的主要内容,如果未能解决你的问题,请参考以下文章

将使用-v7.3(HDF5)保存的Matlab稀疏矩阵加载到Python中并对其进行操作

在 Matlab 中转置 Python 创建的 HDF5 数据集

具有版本控制的 HDF5 文件 (h5py) - 每次保存时都会更改哈希

使用 h5py 将光栅图像添加到 HDF5 文件

如何压缩保存在 hdf5 中的数据?

将大型 Python 数组保存到磁盘以供以后重复使用 --- hdf5?还有啥方法?