保存到hdf5的速度非常慢(Python冻结)
保存到hdf5的速度非常慢(Python冻结)
我正在尝试将瓶颈值保存到一个新创建的hdf5文件中。
瓶颈值以大小为(120,10,10,2048)
的批次形式出现。
保存一个批次就占用了超过16GB的空间,而且Python似乎在那个批次上冻结了。根据最近的发现(见更新部分),似乎hdf5占用大内存是可以接受的,但冻结部分似乎是一个故障。
我只想测试目的,只保存前2个批次,并且只保存训练数据集(再次强调,这是一个测试运行),但我甚至无法通过第一个批次。它只是在第一个批次处停滞不前,不会循环到下一个迭代。如果我尝试检查hdf5,资源管理器会变得迟钝,Python会冻结。如果我试图关闭Python(即使没有检查hdf5文件),Python无法正确关闭,它会强制重新启动。
以下是相关的代码和数据:
总共的数据点大约有90,000个,以每批120个的形式发布。
瓶颈形状是(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) #上述第一部分工作正常 current_iteration = 0 print('created_datasets') for x, y in train: number_of_examples = len(train.filenames) # 图像数 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) # 打印(train_shape) (6831, 10, 10, 2048) # 打印(validation_shape) created_datasets (120, 10, 10, 2048) # 打印(prediction.shape) (120, 12) # label.shape start 0 # 批次开始 end 120 # 批次结束 # 在这里停滞不前,没有打印`print(current_iteration)`
它在这里停滞了一段时间(超过20分钟),而hdf5文件的大小慢慢增长(现在大约20GB),然后我强制终止。实际上,我甚至无法通过任务管理器强制终止,我必须重新启动操作系统才能真正终止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-72GB。如果我尝试更多批次,内存仍然是一样的。所以我猜测train_bottle
根据我分配给数据集的大小参数决定了存储空间,而不是实际填充时。
所以尽管有72GB的内存,但运行得相当快(不到一分钟)。
如果我运行第二行train_labels
,我的内存占用几兆字节。
迭代没有问题,中断语句也被执行。
然而,现在问题来了,如果我尝试同时运行这两行(在我的情况下需要保存'train_bottle'和'train_labels'两者),我会在第一次迭代上冻结,并且不会继续到第二次迭代,即使过了20分钟。Hdf5文件在慢慢增长,但如果我尝试访问它,Windows资源管理器会变得非常慢,我无法关闭Python - 我必须重新启动操作系统。
所以我不确定在同时运行这两行时有什么问题 - 如果我运行内存占用较大的train_data
行,一切都完美地在一分钟内结束。
这篇文章主要讨论了保存数据到hdf5文件的速度慢的问题,并给出了解决方法。具体来说,作者通过使用提供的代码,并按照建议进行了修改,然后测试了hdf5和np.save()的速度。作者的配置是:SSD硬盘为Samsung 960 EVO,CPU为i7-7700K,内存为2133 MHz 16GB,操作系统为Windows 10。测试结果显示,使用HDF5的速度为1339.5 MB/s,而使用np.save()的速度为924.9 MB/s(不使用压缩)。此外,作者还提到,有一位用户在使用lzf-Filter时遇到了问题,如果读者也遇到了这个问题,可以在Windows 10上使用conda分发的python3和pip安装的软件包来运行作者提供的jupyter notebook。最佳的保存/加载大型数组的方法取决于各种因素(最重要的是可实现的压缩比)。在许多情况下,可以通过大幅度地超越HDF5(只有单线程压缩过滤器)来提高性能。此外,吞吐量也可能有很大的差异。使用哪种SSD?它是满的还是空的?数组有多大?(许多SSD有一个快速的SLC-Cache)...
问题原因:保存数据到HDF5文件时非常慢,导致Python卡住。即使HDF5的性能只有np.load()和np.save()的1/5到1/7,但目前没有其他工具或框架能够与其竞争。
解决方法:使用np.load()和np.save()函数直接加载和保存数据,可以获得最快的性能。如果你有足够的DDR内存并且想要极快的数据加载和保存性能,请使用这两个函数。只有使用PCIe SSD才能超越这种解决方案的性能。即使如此,你也需要将np.save()和np.load()与HDF5进行比较,以保持一致。例如,将HDF5函数替换为np.save(),通过这个例子,我可以在我的SATA3 SSD上获得完整的带宽(大约MB/s),几乎没有CPU使用率。但大多数情况下,这并不推荐,因为你几乎失去了HDF5的所有优势(只写或读取文件的部分,压缩)。
作者建议不需要争论np.load()和np.save()和HDF5哪个更快,只需要将HDF5函数替换为np.save()。作者的测试结果显示,np.save()函数的带宽为2.3GBps(18Gbps),是HDF5性能的8倍以上。作者相信你的计算机比他的更快,所以可能达到4~5GBps的速度。请尝试一下,只需要将dset_train_bottle()函数替换为np.save()即可。期待你的测试结果。这并不是一个很大的工作量。
问题出现的原因:
- h5py在没有指定chunkshape的情况下会自动为你创建chunked dataset。但是由于h5py无法知道你如何读取或写入数据集,这经常会导致性能问题。
- 默认的chunk-cache-size为1MB。如果你只写入chunk的一部分,并且该chunk无法适应缓存(使用1MB的chunk-cache-size),整个chunk将被读入内存,修改后再写回磁盘。如果发生多次这样的操作,性能将远远超出HDD/SSD的顺序IO速度。
解决方法:
- 设置一个较大的chunk-cache-size,确保能够读取或写入整个chunk。
- 根据需要修改代码,只在第一维度上读取或写入数据。
- 使用with语句打开HDF5文件时,确保不要多次调用该块,这将关闭并重新打开文件,删除chunk-cache。
- 根据需求确定合适的chunk大小。
文章内容如下:
写入数据到HDF5文件
如果你在没有指定chunkshape的情况下写入chunked dataset,h5py会自动为你创建chunkshape。然而,由于h5py无法知道你如何读取或写入数据集,这经常会导致性能问题。
此外,默认的chunk-cache-size为1MB。如果你只写入chunk的一部分,并且该chunk无法适应缓存(使用1MB的chunk-cache-size),整个chunk将被读入内存,修改后再写回磁盘。如果发生多次这样的操作,性能将远远超出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' # 设置一个较大的chunk-cache-size,确保能够读取或写入整个chunk f = h5c.File(hdf5_path, 'w',chunk_cache_mem_size=1024**2*200) #200 MB cache size # 创建dataset时指定chunks参数,以便进行整块读取或写入 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() # 测试2GB数据的写入性能 for i in range(20): 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)))
编辑:
在循环中创建数据花费了很多时间,因此我在测量时间之前先创建了数据。这样至少可以达到900MB/s的吞吐量(受CPU限制)。对于真实数据和较低的压缩比,你应该可以轻松达到硬盘的顺序IO速度。
使用with语句打开HDF5文件时,如果错误地多次调用该块,会导致性能下降,因为这将关闭并重新打开文件,删除chunk-cache。
关于确定合适的chunk大小,建议参考以下链接:
- [https://stackoverflow.com/a/48405220/4045774](https://stackoverflow.com/a/48405220/4045774)
- [https://stackoverflow.com/a/44961222/4045774](https://stackoverflow.com/a/44961222/4045774)
以上方法应该可以解决问题。如果还有其他建议,请提供相关链接。chunking是我不太熟悉的内容,我会在处理其他事情之后阅读相关资料。非常感谢你详细的回答。我需要再运行一些测试来确保(希望明天能完成)。谢谢你!