将浮点数组写入和附加到 C++ 中 hdf5 文件中的唯一数据集

Posted

技术标签:

【中文标题】将浮点数组写入和附加到 C++ 中 hdf5 文件中的唯一数据集【英文标题】:Writing & Appending arrays of float to the only dataset in hdf5 file in C++ 【发布时间】:2013-03-01 00:55:43 【问题描述】:

我正在处理多个文件,每次处理文件都会输出数千个浮点数组,我会将所有文件的数据存储在单个 hdf5 中的一个巨大数据集中以供进一步处理.

目前我对如何将数据附加到 hdf5 文件感到困惑。 (在上面的代码中注释)在上面的 2 个 for 循环中,如您所见,我想一次将浮点的一维数组附加到 hdf5 中,而不是全部。我的数据是TB,我们只能将数据追加到文件中。

有几个问题:

    在这种情况下如何追加数据?我必须使用什么样的功能? 现在,我有 fdim[0] = 928347543,我尝试将 HDF5 的无穷大标志放入,但运行时执行报错。有没有办法做到这一点?我不想计算我每次拥有的数据;有没有办法只是简单地继续添加数据,而不关心 fdim 的价值?

或者这不可能?

编辑:

我一直在遵循 Simon 的建议,目前这里是我更新的代码:

hid_t desFi5;
hid_t fid1;
hid_t propList;
hsize_t fdim[2];

desFi5 = H5Fcreate(saveFilePath, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);

fdim[0] = 3;
fdim[1] = 1;//H5S_UNLIMITED;

fid1 = H5Screate_simple(2, fdim, NULL);

cout << "----------------------------------Space done\n";

propList = H5Pcreate( H5P_DATASET_CREATE);

H5Pset_layout( propList, H5D_CHUNKED );

int ndims = 2;
hsize_t chunk_dims[2];
chunk_dims[0] = 3;
chunk_dims[1] = 1;

H5Pset_chunk( propList, ndims, chunk_dims );

cout << "----------------------------------Property done\n";

hid_t dataset1 = H5Dcreate( desFi5, "des", H5T_NATIVE_FLOAT, fid1, H5P_DEFAULT, propList, H5P_DEFAULT);

cout << "----------------------------------Dataset done\n";

bufi = new float*[1];
bufi[0] = new float[3];
bufi[0][0] = 0;
bufi[0][1] = 1;
bufi[0][2] = 2;

//hyperslab
hsize_t start[2] = 0,0;
hsize_t stride[2] = 1,1;
hsize_t count[2] = 1,1;
hsize_t block[2] = 1,3;

H5Sselect_hyperslab( fid1, H5S_SELECT_OR, start, stride, count, block);     
cout << "----------------------------------hyperslab done\n";   

H5Dwrite(dataset1, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT, *bufi);

fdim[0] = 3;
fdim[1] = H5S_UNLIMITED;    // COMPLAINS HERE
H5Dset_extent( dataset1, fdim );

cout << "----------------------------------extent done\n";

//hyperslab2
hsize_t start2[2] = 1,0;
hsize_t stride2[2] = 1,1;
hsize_t count2[2] = 1,1;
hsize_t block2[2] = 1,3;

H5Sselect_hyperslab( fid1, H5S_SELECT_OR, start2, stride2, count2, block2);     
cout << "----------------------------------hyperslab2 done\n";  

H5Dwrite(dataset1, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT, *bufi);

cout << "----------------------------------H5Dwrite done\n";        
H5Dclose(dataset1);
cout << "----------------------------------dataset closed\n";   
H5Pclose( propList );   
cout << "----------------------------------property list closed\n"; 
H5Sclose(fid1); 
cout << "----------------------------------dataspace fid1 closed\n";    
H5Fclose(desFi5);       
cout << "----------------------------------desFi5 closed\n";    

我目前的输出是:

bash-3.2$ ./hdf5AppendTest.out
----------------------------------Space done
----------------------------------Property done
----------------------------------Dataset done
----------------------------------hyperslab done
HDF5-DIAG: Error detected in HDF5 (1.8.10) thread 0:
  #000: /home/hdftest/snapshots-bin-hdf5_1_8_10/current/src/H5D.c line 1103 in H5Dset_extent(): unable to set extend dataset
    major: Dataset
    minor: Unable to initialize object
  #001: /home/hdftest/snapshots-bin-hdf5_1_8_10/current/src/H5Dint.c line 2179 in H5D__set_extent(): unable to modify size of data space
    major: Dataset
    minor: Unable to initialize object
  #002: /home/hdftest/snapshots-bin-hdf5_1_8_10/current/src/H5S.c line 1874 in H5S_set_extent(): dimension cannot exceed the existing maximal size (new: 18446744073709551615 max: 1)
    major: Dataspace
    minor: Bad value
----------------------------------extent done
----------------------------------hyperslab2 done
----------------------------------H5Dwrite done
----------------------------------dataset closed
----------------------------------property list closed
----------------------------------dataspace fid1 closed
----------------------------------desFi5 closed

目前,我发现使用 H5Dset_extent 将内容设置为无限制在运行时仍然会导致问题。 (问题函数在上面的代码中用//COMPLAINS HERE标记。)我已经得到了Simon指定的块数据,那么这里有什么问题?

另一方面,如果没有 H5Dset_extent,我可以写一个 [0, 1, 2] 的测试数组就好了,但是我们怎样才能让上面的代码输出测试数组到文件中,如下所示:

[0, 1, 2]
[0, 1, 2]
[0, 1, 2]
[0, 1, 2]
...
...

回想一下:这只是一个测试数组,实际数据更大,我无法将整个数据保存在 RAM 中,因此我必须一次一个地逐个放入数据。

编辑 2:

我更多地听从了西蒙的建议。这是关键部分:

hsize_t n = 3, p = 1;
float *bufi_data = new float[n * p];
float ** bufi = new float*[n];
for (hsize_t i = 0; i < n; ++i)
    bufi[i] = &bufi_data[i * n];


bufi[0][0] = 0.1;
bufi[0][1] = 0.2;
bufi[0][2] = 0.3;

//hyperslab
hsize_t start[2] = 0,0;
hsize_t count[2] = 3,1;

H5Sselect_hyperslab( fid1, H5S_SELECT_SET, start, NULL, count, NULL);
cout << "----------------------------------hyperslab done\n";   

H5Dwrite(dataset1, H5T_NATIVE_FLOAT, H5S_ALL, fid1, H5P_DEFAULT, *bufi);

bufi[0][0] = 0.4;
bufi[0][1] = 0.5;
bufi[0][2] = 0.6;

hsize_t fdimNew[2];
fdimNew[0] = 3;
fdimNew[1] = 2;
H5Dset_extent( dataset1, fdimNew );

cout << "----------------------------------extent done\n";

//hyperslab2
hsize_t start2[2] = 0,0; //PROBLEM
hsize_t count2[2] = 3,1;

H5Sselect_hyperslab( fid1, H5S_SELECT_SET, start2, NULL, count2, NULL);     
cout << "----------------------------------hyperslab2 done\n";  

H5Dwrite(dataset1, H5T_NATIVE_FLOAT, H5S_ALL, fid1, H5P_DEFAULT, *bufi);

从上面,我得到了 hdf5 的以下输出:

0.4 0.5 0.6
  0   0   0

在进一步试验start2count2 后,我发现这些变量只影响bufi 的起始索引和递增索引。它根本不会移动我的数据集的写作索引的位置。

回想一下:最终结果一定是:

0.1 0.2 0.3
0.4 0.5 0.6

另外,对于 H5Dwrite,Simon,它必须是 bufi 而不是 *bufi,因为 bufi 给了我完全随机的数字。

更新 3:

对于西蒙建议的选择部分:

hsize_t start[2] = 0, 0;
hsize_t count[2] = 1, 3;

hsize_t start[2] = 1, 0;
hsize_t count[2] = 1, 3;

这些将给出以下错误:

HDF5-DIAG: Error detected in HDF5 (1.8.10) thread 0:
  #000: /home/hdftest/snapshots-bin-hdf5_1_8_10/current/src/H5Dio.c line 245 in H5Dwrite(): file selection+offset not within extent
    major: Dataspace
    minor: Out of range

count[2] 应该是3,1,而不是1,3,我想?而对于start[2],如果我不设置为0,0,它总是会喊出上面的错误。

你确定这是正确的吗?

【问题讨论】:

【参考方案1】:

在这种情况下如何追加数据?我必须使用什么样的功能?

您必须使用 hyperslabs。这就是您只需要编写数据集的一部分。 执行此操作的函数是 H5Sselect_hyperslab。在fd1 上使用它并在H5Dwrite 调用中使用fd1 作为您的文件数据空间。

我试过把 HDF5 的 infinity flag 放进去,但是运行时执行报错。

您需要创建一个分块数据集,以便能够将其最大大小设置为无穷大。创建一个数据集创建属性列表并使用H5Pset_layout 使其分块。使用H5Pset_chunk 设置块大小。然后使用此属性列表创建您的数据集。

我不想每次都计算我拥有的数据;有没有办法只是简单地继续添加数据,而不关心fdim 的值?

你可以做两件事:

    预先计算最终大小,以便创建足够大的数据集。看起来这就是你正在做的事情。

    使用H5Dset_extent 随时扩展您的数据集。为此,您需要将最大维度设置为无穷大,因此您需要一个分块数据集(见上文)。

在这两种情况下,您都需要在 H5Dwrite 调用中选择文件数据空间上的 hyperslab(见上文)。

演练working code

#include <iostream>
#include <hdf5.h>

// Constants
const char saveFilePath[] = "test.h5";
const hsize_t ndims = 2;
const hsize_t ncols = 3;

int main()

首先,创建一个 hdf5 文件。

    hid_t file = H5Fcreate(saveFilePath, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
    std::cout << "- File created" << std::endl;

然后创建一个 2D 数据空间。 第一个维度的大小是无限的。我们最初将其设置为 0 以显示如何在每个步骤中扩展数据集。例如,您还可以将其设置为您要写入的第一个缓冲区的大小。 第二维的大小是固定的。

    hsize_t dims[ndims] = 0, ncols;
    hsize_t max_dims[ndims] = H5S_UNLIMITED, ncols;
    hid_t file_space = H5Screate_simple(ndims, dims, max_dims);
    std::cout << "- Dataspace created" << std::endl;

然后创建一个数据集创建属性列表。 使用无限维度时,必须对数据集的布局进行分块。 块大小的选择会影响性能,包括时间和磁盘空间。如果块非常小,您将有很多开销。如果它们太大,您可能会分配不需要的空间,并且您的文件最终可能会太大。 这是一个玩具示例,因此我们将选择一行的块。

    hid_t plist = H5Pcreate(H5P_DATASET_CREATE);
    H5Pset_layout(plist, H5D_CHUNKED);
    hsize_t chunk_dims[ndims] = 2, ncols;
    H5Pset_chunk(plist, ndims, chunk_dims);
    std::cout << "- Property list created" << std::endl;

创建数据集。

    hid_t dset = H5Dcreate(file, "dset1", H5T_NATIVE_FLOAT, file_space, H5P_DEFAULT, plist, H5P_DEFAULT);
    std::cout << "- Dataset 'dset1' created" << std::endl;

关闭资源。数据集现已创建,因此我们不再需要属性列表。 我们不再需要文件数据空间,因为当数据集被扩展时,它将变得无效,因为它仍将保留以前的范围。 所以无论如何我们都必须获取更新后的文件数据空间。

    H5Pclose(plist);
    H5Sclose(file_space);

我们现在将两个缓冲区附加到数据集的末尾。 第一个将是两行长。 第二个将是三行。

第一个缓冲区

我们创建一个 2D 缓冲区(在内存中连续,行主要顺序)。 我们将分配足够的内存来存储 3 行,所以我们可以重用缓冲区。 让我们创建一个指针数组,以便我们可以使用b[i][j] 表示法 而不是buffer[i * ncols + j]。这纯粹是审美。

    hsize_t nlines = 3;
    float *buffer = new float[nlines * ncols];
    float **b = new float*[nlines];
    for (hsize_t i = 0; i < nlines; ++i)
        b[i] = &buffer[i * ncols];
    

缓冲区中要写入数据集的初始值:

    b[0][0] = 0.1;
    b[0][1] = 0.2;
    b[0][2] = 0.3;
    b[1][0] = 0.4;
    b[1][1] = 0.5;
    b[1][2] = 0.6;

我们创建一个内存数据空间来指示内存中缓冲区的大小。 记住第一个缓冲区只有两行长。

    dims[0] = 2;
    dims[1] = ncols;
    hid_t mem_space = H5Screate_simple(ndims, dims, NULL);
    std::cout << "- Memory dataspace created" << std::endl;

我们现在需要扩展数据集。 我们将数据集的初始大小设置为 0x3,因此我们需要先扩展它。 请注意,我们扩展了数据集本身,而不是它的数据空间。 记住第一个缓冲区只有两行长。

    dims[0] = 2;
    dims[1] = ncols;
    H5Dset_extent(dset, dims);
    std::cout << "- Dataset extended" << std::endl;

在文件数据集上选择 hyperslab。

    file_space = H5Dget_space(dset);
    hsize_t start[2] = 0, 0;
    hsize_t count[2] = 2, ncols;
    H5Sselect_hyperslab(file_space, H5S_SELECT_SET, start, NULL, count, NULL);
    std::cout << "- First hyperslab selected" << std::endl;

将缓冲区写入数据集。 mem_spacefile_space 现在应该选择相同数量的元素。 注意buffer&amp;b[0][0] 是等价的。

    H5Dwrite(dset, H5T_NATIVE_FLOAT, mem_space, file_space, H5P_DEFAULT, buffer);
    std::cout << "- First buffer written" << std::endl;

我们现在可以关闭文件数据空间。 我们现在可以关闭内存数据空间并为第二个缓冲区创建一个新的, 但我们会简单地更新它的大小。

    H5Sclose(file_space);

第二个缓冲区

缓冲区中要附加到数据集的新值:

    b[0][0] = 1.1;
    b[0][1] = 1.2;
    b[0][2] = 1.3;
    b[1][0] = 1.4;
    b[1][1] = 1.5;
    b[1][2] = 1.6;
    b[2][0] = 1.7;
    b[2][1] = 1.8;
    b[2][2] = 1.9;

调整内存数据空间的大小以指示缓冲区的新大小。 第二个缓冲区是三行长。

    dims[0] = 3;
    dims[1] = ncols;
    H5Sset_extent_simple(mem_space, ndims, dims, NULL);
    std::cout << "- Memory dataspace resized" << std::endl;

扩展数据集。 请注意,在这个简单的示例中,我们知道 2 + 3 = 5。 通常,您可以从文件数据空间中读取当前范围 并添加所需的行数。

    dims[0] = 5;
    dims[1] = ncols;
    H5Dset_extent(dset, dims);
    std::cout << "- Dataset extended" << std::endl;

在文件数据集上选择 hyperslab。 同样在这个简单的例子中,我们知道 0 + 2 = 2。 通常,您可以从文件数据空间中读取当前范围。 第二个缓冲区是三行长。

    file_space = H5Dget_space(dset);
    start[0] = 2;
    start[1] = 0;
    count[0] = 3;
    count[1] = ncols;
    H5Sselect_hyperslab(file_space, H5S_SELECT_SET, start, NULL, count, NULL);
    std::cout << "- Second hyperslab selected" << std::endl;

将缓冲区附加到数据集

    H5Dwrite(dset, H5T_NATIVE_FLOAT, mem_space, file_space, H5P_DEFAULT, buffer);
    std::cout << "- Second buffer written" << std::endl;

结束:让我们关闭所有资源:

    delete[] b;
    delete[] buffer;
    H5Sclose(file_space);
    H5Sclose(mem_space);
    H5Dclose(dset);
    H5Fclose(file);
    std::cout << "- Resources released" << std::endl;


注意: 我删除了之前的更新,因为答案太长了。如果您有兴趣,请浏览历史。

【讨论】:

我已经按照您迄今为止的所有建议更新了上述问题中的代码。 我已经更新了我的问题,也关注了您迄今为止的所有更新。 好的@Karl,这次我自己尝试了一下,我发布了一个工作代码的要点。 谢谢你,西蒙。看完全篇之后,我现在看到了所有我一直误会的地方。我的天... 这样的答案让我想开一个虚拟账户,这样我就可以投票两次......

以上是关于将浮点数组写入和附加到 C++ 中 hdf5 文件中的唯一数据集的主要内容,如果未能解决你的问题,请参考以下文章

在 fortran 中将写入附加到 hdf5 文件

如何使用 C++ API 在 HDF5 文件中写入/读取锯齿状数组?

如何有效地将数据附加到 C 中的 HDF5 表?

使用 Visual C++ 将二维数组 int[n][m] 写入 HDF5 文件

HDF5:如何将数据附加到数据集(可扩展数组)

如何将自定义类型的列表/数组写入 HDF5 文件?