使用Python写HDF5文件的最快方法是什么?

25

如何在保持内存使用合理的情况下,将一个大小为10 GB的混合文本/数字的CSV文件快速转换成HDF5格式?如果可能的话,我想使用h5py模块。

在下面的示例中,我找到了一种非常慢和非常快的方法来将数据写入HDF5。是否最好将数据分段为每个10,000行进行写入HDF5?或者有更好的方法将大量数据写入此类文件吗?

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

将数据读入numpy数组,并通过发送整个数组来避免循环。 - Benjamin
1
@Benjamin:如果数组太大,内存无法容纳怎么办? - Nicholas Palko
我认为您需要告诉我们您希望如何构建您的HDF5文件结构。 - Winston Ewert
然后将其分块读入,尽可能大的块,并使用循环(也许10次迭代?)而不是逐个单元格进行。顺便说一句,我在内存中保存超过2500万个浮点数数组没有任何问题。 - Benjamin
3个回答

8
我建议不要分块数据,而是将数据存储为一系列单个数组数据集(按照Benjamin的建议)。我刚刚将一个企业应用程序的输出加载到HDF5中,并能够将大约45亿个复合数据类型打包成45万个数据集,每个集合包含10000个数据的数组。现在写入和读取似乎非常快速,在我最初尝试分块数据时速度非常慢。这只是我的一些想法!
更新:
以下是我实际代码中提取的几个片段(我正在使用C而不是Python进行编码,但您应该了解我正在做什么)。我只是在数组中写入长无符号整数(每个数组有10000个值),并在需要实际值时将它们读回来。
这是我的典型编写器代码。在这种情况下,我只是将长无符号整数序列写入一个数组序列中,并在创建每个数组序列时将其加载到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语言中,这很简单,而且读写时间显著优于分块数据集解决方案。另外,由于无法对复合数据集使用压缩,因此分块的表面优势是无效的,因此所有复合数据都以相同方式存储。

如果可以的话,您能否详细介绍一下您的数据结构是如何组织的?如果您能够提供一个具体的(代码)示例,我将非常乐意接受这个答案。 - Nicholas Palko
我已经更新了我的回复并附上了代码。如果这有帮助,请告诉我! - Marc
你的代码看起来很棒,但是它是单线程的吗?你知道如何在Spark下编写HDF5吗? - vy32

5

利用numpy.loadtxt函数的灵活性,可以将文件中的数据读取到一个numpy数组中,这个数组非常适合作为初始化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)

这似乎需要大量的内存,与OP的目标相反...? - Duncan MacIntyre

3
我不确定这是否是最有效的方法(我从未使用过;我只是汇集了一些我独立使用过的工具),但你可以使用matplotlib csv助手方法将csv文件读入numpy recarray中。
你可能还可以找到一种按块读取csv文件以避免将整个文件加载到磁盘中的方法。然后使用recarray(或其中的切片)将整个文件(或大块文件)写入h5py数据集。我不确定h5py如何处理recarrays,但文档表明应该没问题。
基本上,如果可能,请尝试一次性写入大块数据,而不是迭代单个元素。
另一种读取csv文件的可能性只是numpy.genfromtxt
你可以使用关键字usecols获取所需的列,并通过正确设置skip_headerskip_footer关键字来仅读取指定的一组行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接