Python -HDF 5到numpy数组-内存不足

unguejic  于 12个月前  发布在  Python
关注(0)|答案(3)|浏览(108)

我有一个HDF5文件,包含2000000行,每行有8个float32列。原始内存的总大小应该大约为640MB。
我想在我的Python应用程序中加载这些数据,但是,在加载到numpy数组的过程中,我耗尽了内存(我有64GB RAM)
我使用这个代码:

import h5py

hf = h5py.File(dataFileName, 'r')
data = hf['data'][:]

对于较小的文件,它工作正常,但是,我的输入不是那么大,以及。那么有没有其他方法可以将整个数据集加载到内存中,因为它应该适合没有任何问题。另一方面,为什么它需要这么多内存?即使它会在内部将float32转换为float64,它也不会接近整个RAM的大小。
数据集信息来自HDFView 3.3.0

ca1c2owp

ca1c2owp1#

出现问题的第一个迹象可能是文件虽然包含大约500 MiB的数据,但实际上大小大约为850 MiB;至少在我把它复制到我的系统上时是这样。这表明开销过大。
微小的块大小加上相当大的数据集大小显然会破坏HDF 5库,或者至少会让它分配大量的内存。作为测试,如果我没有足够快地杀死进程,这将消耗系统上的所有内存和交换:

data = np.random.random((16409916, 8)).astype('f4')
with h5py.File(outpath, "w") as outfile:
    dset = outfile.create_dataset("data", data=data, chunks=(2, 8))

这样做是可行的,但速度非常慢:

with h5py.File(outpath, "w") as outfile:
    dset = outfile.create_dataset(
            "data", shape=data.shape, dtype=data.dtype, chunks=(2, 8))
    for start in range(0, len(data), 2):
        end = start + 2
        dset[start:end] = data[start:end]

同样,您也不能用如此可笑的块大小一次读取所有内容。如果要我猜原因的话,库可能想在阅读它们之前弄清楚所有块的位置。这会将磁盘上相当紧凑的表示转换为内存中的大表示。
尝试以下方法作为解决方法:

with h5py.File(inpath, "r") as infile:
    dset = infile["data"]
    shape, dtype = dset.shape, dset.dtype
    data = np.empty(shape, dtype)
    raw_chunksize = 1024**2 # 1 MiB
    raw_rowsize = dtype.itemsize * np.prod(shape[1:])
    chunksize = max(1, raw_chunksize // raw_rowsize)
    for start in range(0, len(data), chunksize):
        end = start + chunksize
        data[start:end] = dset[start:end]

请告诉创建这些文件的人仔细阅读块大小的含义并选择一个合适的;通常在64 kB到1 MiB的范围内。

gzszwxb4

gzszwxb42#

你说的没错。如果您仅将单个640 MB数据集加载到具有64 GB RAM的系统上的单个NumPy数组,则不应耗尽内存。如果你是,问题可能在其他地方。您是否同时加载了很多数组?你的程序是否有其他需要大量内存的对象?是否有其他运行的应用程序也消耗大量内存?
首先用一个简单的测试用例诊断行为。我写了一个程序来创建一个包含1个数据集的HDF 5文件,关闭文件,然后打开并将其读入1个数组。这运行在我的系统与24 GB内存,并应运行在您的系统。(代码在最后。)如果它对你有效,那就确认了你可以读这个大小的数组,问题出在你程序的其他地方。如果它不运行,那么在您的系统上使用HDF 5/h5 py会出现更大的问题。
注意:您还可以通过创建数据集对象来减少内存使用。这些“行为类似”NumPy数组,但内存占用要小得多。这也在代码中得到了证明。

验证码:

a0, a1 = 20_000_000, 8
arr = np.random.random(a0*a1).reshape(a0,a1).astype(np.float32)
with h5py.File('SO_76906733.h5','w') as h5f:
    h5f.create_dataset('test',data=arr)
    
with h5py.File('SO_76906733.h5','r') as h5f:
    print(h5f['test'].dtype, h5f['test'].shape)
    dset = h5f['test']  ## creates h5py dataset object
    print(f'Dataset memory used: {sys.getsizeof(dset)}')
    data = h5f['test'][()]  ## creates numpy array object
    print(f'Array memory used: {sys.getsizeof(data)}')
wtzytmuj

wtzytmuj3#

如果你不能让“某人”为你重新创建文件,你可以自己做。下面的代码使用@Homer512建议的相同方法来复制数据。它使用chunks=(30_000,8)创建一个新文件和数据集。这将提供给予更好的I/O性能。新文件中数据集的读取时间小于一秒。
我的代码中的计时和打印语句不是必需的。我把它们包括进来是为了在跑步时得到一些反馈。请注意,36行的行读取因子适用于此数据集,并避免在最后一次循环中进行增量读取。你将不得不调整它以适应更一般的情况。
代码如下:

start = time.time()
row_fact = 36
with h5py.File('smallchunks.h5','r') as h5r, \
     h5py.File('largechunks.h5','w') as h5w:
    dset = h5r['test']
    data = np.empty(dset.shape, dset.dtype)
    # first, read dataset into an array
    for i in range(dset.shape[0]//row_fact):
        data[i*36:i*row_fact+row_fact] = dset[i*row_fact:i*row_fact+row_fact]
        if not (i % 50_000):
            print(f'time to read to row {i*row_fact+row_fact:,}: {time.time()-start:.2f}')    

    print(f'time to read small chunked file: {time.time()-start:.2f}\n')    

    start = time.time()
    h5w.create_dataset('test', data=data, maxshape=(None,8), chunks=(30_000,8))
    print(f'time to create large chunked file: {time.time()-start:.2f}\n')

start = time.time()   
with h5py.File('largechunks.h5','r') as h5f:
    data = h5f['test'][()]
print(f'time to read large chunked file: {time.time()-start:.2f}')

相关问题