【问题标题】:mmap hdf5 dataset in C/C++C/C++ 中的 mmap hdf5 数据集
【发布时间】:2017-10-08 21:18:35
【问题描述】:

我有一个巨大的 hdf5 文件(~100GB,连续存储),我需要随机访问不同的点。在 python/h5py 或 C/H5Dread 中使用索引似乎很慢,因此我想直接映射数据。

事实上,这在我的本地 64 位 Fedora 25 上的 h5py/numpy 中有效,遵循this。但是在远程集群上,numpy/mmap 对大文件 ([Errno 12] Cannot allocate memory) 失败,即使 python 似乎是 64 位并且在 C 中使用 mmap 对 100GB 文件进行简单测试也可以。所以我集群的 Python 可能有问题。

我看到的一个解决方案是在 C 中使用 mmap。我写了一个小的 test 来创建一个带有一维数据集的小型 hdf5,并使用“H5Dget_offset”获取数据集偏移量。但是,结果并不正确。

以下是核心代码:

/* Get dataset offset within file */
file_id = H5Fopen (FILE, H5F_ACC_RDONLY, H5P_DEFAULT);
dataset_id = H5Dopen2(file_id, "/dset", H5P_DEFAULT);
offset = H5Dget_offset(dataset_id);

fd = open(FILE, O_RDONLY);
// align with page size
pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
length = NX * NY * sizeof(int);
addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
          MAP_PRIVATE, fd, pa_offset);

blog 下的讨论提到了 Julia 中通过H5Fget_vfd_handleH5Dget_offset 实现这一点的实现,但我没有找到详细/简单的解释。

  • 我通过 python/h5py 的 dataset.id.get_offset 获得的偏移量与我通过 C 中的 H5Dget_offset 获得的偏移量相同。
  • 我认为我的核心问题是:如何使用 C 的H5Dget_offset 给出的偏移量来映射数据集。
  • 首先,mmap 是否应该比单纯的 hdf5 访问快得多?

【问题讨论】:

  • 为什么内存映射而不是仅仅寻找和阅读?
  • @tadman ,“寻找和阅读”是指直接索引吗?我试过了,但速度很慢。我不知道索引是否会产生一些额外的开销?对我来说,每次只随机访问一个数据点,不断循环,而不是切片,这是hdf5使用的典型情况。
  • 如果您在unbuffered mode 中打开文件,那么您可以直接、原始、低级别地访问该文件。使用fseek/fread,您可以从任意位置获取数据,随机访问。默认情况下,文件读取是缓冲的,这可能会拖累性能,除非您进行线性读取。
  • 谢谢@tadman。我将尝试无缓冲模式。但是,我觉得我的问题是从H5Dget_offset 返回的偏移量并不完全是从我通过open 打开的文件头到实际数据空间开头的偏移量(代码中的文件描述符fd) .也许这个偏移量必须与H5Fget_vfd_handle 返回的地址一起使用,但是我还没有弄清楚如何使用该地址(如我所见,这是文件描述符的一些地址)。无论如何,使用fseek/fread 仍然需要正确的偏移量。
  • 我不确定该文件中发生了什么,但可能存在不应计入偏移量的标题或某些框架。在许多二进制文件格式中,存在维护自己独立偏移量的嵌套结构。

标签: c numpy hdf5 mmap


【解决方案1】:

问题的主要原因与 HDF 库无关。您没有将 HDF 库告诉您的字节映射到数据集。

H5Dget_offset 返回从文件开头到相关数据集开头的偏移量(以字节为单位)。但是您没有将该值传递给mmap(2)。您正在计算 actual 偏移量下方的页面大小的倍数,然后在您的 mmap(2) 调用中使用 that 作为文件的偏移量。

代替:

mmap(..., pa_offset);

你应该有

mmap(..., offset);

至于这是否会更快。 HDF 库很复杂。可能会有很多开销(边界检查、权限检查、其他库调用),但它也可能得到了相当好的优化。确定内存映射是否更快的唯一合理方法是对其进行测量。

【讨论】:

  • 我认为 mmap 本身仍然需要 pa_offset 每个 Posix 规范。错误是返回的指针应该向后移动,例如,int * ptr = (int *) (addr + offset - pa_offset);。事实上,直接在mmap 中使用offset 在我的测试中会出现分段错误。
  • @Liang 哇,我真的不知道offset 必须是页面大小的倍数。我猜我使用的实现不符合 POSIX 标准!您发布的解决方案与我试图完成的事情相同,但合规。
  • 对。有趣的是,在我对微小数据(15 个整数)的测试中,因此可能开销很小,pa_offset 为零,因为与页面大小相比,真正的offset 太小了。
【解决方案2】:

这是我自己对这个问题的回答。

按照HDS 的实现,我发现了我原始代码中的一个错误,但解决方案与@bnaecker 的不同。

基本上,原来的mmap 仍然需要pa_offset,根据mmap doc。但是返回的指针应该被移回,例如,

int * ptr = (int *) (addr + offset - pa_offset);

我这里贴出三种访问方式的核心代码供以后用户参考。特别是 tadman 提到的 setvbuf 技巧可能会进一步提高随机访问的性能(但未经测试)。

FILE *fp;

/* Get dataset offset within file */
file_id = H5Fopen(FNAME, H5F_ACC_RDONLY, H5P_DEFAULT);
status = H5Fget_vfd_handle(file_id, H5P_DEFAULT, &fhandle);
dataset_id = H5Dopen(file_id, "/dset", H5P_DEFAULT);
offset = H5Dget_offset(dataset_id);

/* Read through stdio */
fp = fopen(FNAME, "rb");
fseek(fp, offset, SEEK_SET);
int x0[NX*NY];
fread(&x0, sizeof(int), NX*NY, fp);

/* Get the file descriptor */
fd = *((int *)fhandle);

/* Read through Posix */
int x1[NX*NY];
lseek(fd, offset, SEEK_SET);
read(fd, x1, NX*NY*sizeof(int));

/* Read through mmap */
// page size-aligned offset for mmap
pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
length = NX * NY * sizeof(int);
addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
          MAP_PRIVATE, fd, pa_offset);
// revert the align for correct access
int * x2 = (int *) (addr + offset - pa_offset);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-12-10
    • 2022-10-12
    • 2011-08-24
    • 2021-04-23
    • 2014-05-12
    • 2017-06-02
    • 2017-12-01
    • 2018-11-20
    相关资源
    最近更新 更多