用Python写HDF5文件的最快方法?

26 投票
3 回答
37199 浏览
提问于 2025-04-16 14:37

假设你有一个很大的CSV文件,大小在几十GB,里面混合了文本和数字。你想要找到一种最快的方法,把这些内容转存到HDF5文件里,同时又不想占用太多内存。

如果可以的话,我想使用h5py这个模块。

在下面这个简单的例子中,我发现了两种写入HDF5数据的方法:一种非常慢,另一种则非常快。请问,分批写入,比如每次写1万行,是否是最佳做法?还是说有更好的方法来处理这么大量的数据?

import h5py

n = 10000000
f = h5py.File('foo.h5','w')
dset = f.create_dataset('int',(n,),'i')

# this is terribly slow
for i in xrange(n):
  dset[i] = i

# instantaneous
dset[...] = 42

3 个回答

3

我不太确定这是不是最有效的方法(而且我也没用过;我只是把一些我独立使用过的工具整理在一起),不过你可以用 matplotlib 提供的工具来读取 csv 文件,把它读成一个 numpy 的记录数组(recarray)。

你可能还可以找到一种方法,把 csv 文件分块读取,这样就不用一次性把整个文件加载到内存里。然后可以用这个记录数组(或者其中的一部分)把整个数据(或者大部分数据)写入 h5py 数据集。我不太确定 h5py 是怎么处理记录数组的,但文档上说应该没问题。

总的来说,如果可以的话,尽量一次性写入大块数据,而不是一个一个元素地写。

读取 csv 文件的另一种方法是直接使用 numpy.genfromtxt

你可以通过关键词 usecols 来选择你想要的列,然后通过正确设置 skip_headerskip_footer 关键词,只读取指定的行。

5

使用 numpy.loadtxt 的灵活性,可以从文件中读取数据,并将其存储到一个 numpy array(即一个数组)中。这个数组非常适合用来初始化 hdf5 数据集。

import h5py
import numpy as np

d = np.loadtxt('data.txt')
h = h5py.File('data.hdf5', 'w')
dset = h.create_dataset('data', data=d)
8

我建议不要把数据分块,而是将数据存储为一系列单独的数组数据集(就像本杰明所提到的那样)。我刚刚把我正在开发的一个企业应用的输出加载到HDF5中,成功地将大约45亿个复合数据类型打包成了450,000个数据集,每个数据集包含10,000个数据。现在写入和读取的速度看起来非常快,但当我最开始尝试分块数据时,速度非常慢。

只是一个想法!

更新:

这里有几段我实际代码的片段(我用的是C语言而不是Python,但你应该能明白我在做什么),并且为了清晰做了一些修改。我只是把长整型无符号整数写入数组中(每个数组10,000个值),当我需要实际值时再读取它们。

这是我典型的写入代码。在这个例子中,我只是把长整型无符号整数序列写入一系列数组,并在创建每个数组序列时将其加载到HDF5中。

//Our dummy data: a rolling count of long unsigned integers
long unsigned int k = 0UL;
//We'll use this to store our dummy data, 10,000 at a time
long unsigned int kValues[NUMPERDATASET];
//Create the SS adata files.
hid_t ssdb = H5Fcreate(SSHDF, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
//NUMPERDATASET = 10,000, so we get a 1 x 10,000 array
hsize_t dsDim[1] = {NUMPERDATASET};
//Create the data space.
hid_t dSpace = H5Screate_simple(1, dsDim, NULL);
//NUMDATASETS = MAXSSVALUE / NUMPERDATASET, where MAXSSVALUE = 4,500,000,000
for (unsigned long int i = 0UL; i < NUMDATASETS; i++){
    for (unsigned long int j = 0UL; j < NUMPERDATASET; j++){
        kValues[j] = k;
        k += 1UL;
    }
    //Create the data set.
    dssSet = H5Dcreate2(ssdb, g_strdup_printf("%lu", i), H5T_NATIVE_ULONG, dSpace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
    //Write data to the data set.
    H5Dwrite(dssSet, H5T_NATIVE_ULONG, H5S_ALL, H5S_ALL, H5P_DEFAULT, kValues);
    //Close the data set.
    H5Dclose(dssSet);
}
//Release the data space
H5Sclose(dSpace);
//Close the data files.
H5Fclose(ssdb);

这是我读取代码的一个稍微修改过的版本。其实有更优雅的方法来实现这个(比如,我可以用超平面来获取值),但这是在我相对严格的敏捷/BDD开发过程中最简单的解决方案。

unsigned long int getValueByIndex(unsigned long int nnValue){
    //NUMPERDATASET = 10,000
    unsigned long int ssValue[NUMPERDATASET];
    //MAXSSVALUE = 4,500,000,000; i takes the smaller value of MAXSSVALUE or nnValue
    //to avoid index out of range error 
    unsigned long int i = MIN(MAXSSVALUE-1,nnValue);
    //Open the data file in read-write mode.
    hid_t db = H5Fopen(_indexFilePath, H5F_ACC_RDONLY, H5P_DEFAULT);
    //Create the data set. In this case, each dataset consists of a array of 10,000
    //unsigned long int and is named according to its integer division value of i divided
    //by the number per data set.
    hid_t dSet = H5Dopen(db, g_strdup_printf("%lu", i / NUMPERDATASET), H5P_DEFAULT);
    //Read the data set array.
    H5Dread(dSet, H5T_NATIVE_ULONG, H5S_ALL, H5S_ALL, H5P_DEFAULT, ssValue);
    //Close the data set.
    H5Dclose(dSet);
    //Close the data file.
    H5Fclose(db);
    //Return the indexed value by using the modulus of i divided by the number per dataset
    return ssValue[i % NUMPERDATASET];
}

主要的要点是写入代码中的内部循环,以及整数除法和取余操作,用来获取数据集数组的索引和该数组中所需值的索引。如果这足够清楚,让我知道你是否能用h5py做出类似或更好的东西。在C语言中,这非常简单,读写速度比分块数据集的解决方案要快得多。而且因为我根本不能在复合数据集上使用压缩,所以分块的明显好处就不成立了,因此我所有的复合数据都是以相同的方式存储的。

撰写回答