【问题标题】:How to efficiently read binary data from files that has complex structure in C++如何有效地从 C++ 中具有复杂结构的文件中读取二进制数据
【发布时间】:2019-05-14 19:45:42
【问题描述】:

我正在编写一段代码以使用 C++ IOStreams 读取跨越多个文件的数 GB 数据,出于多种设计原因,我选择了 C++ IOStreams,而我不会让您感到厌烦。由于数据是由运行我的代码的同一台机器上的单独程序生成的,因此我相信在大多数情况下可以忽略与字节序相关的问题。

文件的结构相当复杂。例如,有一个标头描述了特定二进制配置的记录数。在文件的后面,我必须使代码有条件地读取该行数。这种模式以一种复杂但有据可查的方式重复出现。

我的问题与如何有效地执行此操作有关 - 我确定我的过程将受到 IO 限制,所以我的直觉是,而不是读取小块中的数据,例如以下方法

std::vector<int> buffer;
buffer.reserve(500);
file.read( (char*)&buffer[0], 500 * sizeof(int));

我应该一次完全读取一个文件并尝试在内存中处理它。所以我的相关问题:

  • 鉴于这似乎意味着读取 char* 或 std::vector 数组,您最好如何将此数组转换为正确表示文件结构所需的数据格式?
  • 我的假设不正确吗?

我知道显而易见的答案是先尝试,然后再进行分析,我当然会进行分析。但这个问题更多的是关于如何在一开始就选择正确的方法——一种“选择正确的算法”优化,而不是我以后发现瓶颈后可以设想的那种优化!

我会对提供的答案感兴趣 - 我往往只能找到相对简单的二进制文件的答案,而上述方法适用于这些文件。我的问题是,大部分二进制数据是根据文件头中的数字有条件地构建的(即使头是这样格式化的!)所以我需要能够更仔细地处理文件。

提前致谢。

编辑: 一些关于内存映射的 cmets - 看起来不错,但不知道该怎么做,我读过的所有内容都告诉我它不可移植。我有兴趣尝试 mmap,但也有兴趣更便携的解决方案(如果有的话!)

【问题讨论】:

  • 要考虑的一个选项是将整个文件映射到内存中,然后使用字符或小块。
  • 非常有趣.. :) 您能否详细说明“二进制数据的结构取决于文件标题中的数字”,例如是否像标题描述该文件中的整数一样简单?
  • @thekashyap:不完全是。该文件描述了某些对象,并将使用不同结构的二进制记录来这样做。作为一个简化的例子,它可能表明有 10 只猫和 5 个猫瓣。 “cat”记录的结构可能是(int numWhiskers,int numLives,double tailLength),而“catflap”可能是(double height, double width, int numberOfOpenings)。然后标题告诉我当我到达文件的那个位时要读取的每条记录的数量。我希望这能让它更清楚!
  • 您是否总是需要按相同顺序排列对象?为什么不直接读取大块文件并将它们传递给解析器?
  • @DavidSchwartz:不知道你的意思——文件以特定的顺序向我展示对象,所以我就是这样阅读它们的。认为我误解了你在说什么:)

标签: c++ performance binary binaryfiles


【解决方案1】:

使用 64 位操作系统和内存映射文件。如果您还需要支持 32 位操作系统,请使用可根据需要映射文件块的兼容层。

或者,如果您总是需要文件顺序的对象,只需编写一个理智的解析器来处理块中的对象。像这样:

1) 读入 512KB 的文件。

2) 从我们读取的数据中提取尽可能多的对象。

3) 根据需要读入尽可能多的字节以将缓冲区填充回 512KB。如果我们根本没有读取任何字节,请停止。

4) 转到步骤 3。

【讨论】:

  • 谢谢 - 有没有办法以便携的方式做到这一点?
  • 读取原始二进制文件无法从一种架构移植到另一种架构。
  • @Fritz 没有办法以便携的方式执行您建议的任何操作。
  • @DavidSchwartz:啊,好吧 - 所以如果我想要便携性,我需要以(可能)更慢的方式来做。否则,我需要将自己绑定到特定的操作系统,或者使用编译器开关支持多种方法。可能解释了为什么我一直在努力寻找任何东西!
  • 对,更新的分步方法是我在上面想要得到的:我的问题是,如果我将 512K 拉入一个类似字符的缓冲区,并且在中间有(例如)10条{int32,int32,float,double float}的记录,将我的char数据从数组转换为我的记录格式的最理智/有效的方法是什么?举个简单的例子,将 8 个字符变成双精度的最有效方法是什么?
【解决方案2】:

您可以mmap 一些文件段(或整个文件,至少在 64 位机器上)。也许使用madvise 和(在单独的线程中)readahead

【讨论】:

    【解决方案3】:

    我想你已经有足够的东西开始了,只要你有足够的 RAM,内存映射肯定是一个好主意。否则大块阅读。

    一旦数据在整个文件或大块内存中可用,最简单的读取方法是:

    • 定义适当的结构
    • 在加载数据的内存中创建一个指向适当偏移量的指针
    • reinterpret_cast 指向“适当结构”类型指针或适当结构数组的指针。

    如果需要,您可以使用#pragmas 来确保包装尺寸/订单等。但这又取决于操作系统/编译器。

    【讨论】:

    • 啊,内存中的偏移量指针!当然!这就是我所追求的!谢谢大家(当我可以投票时,我会的!)
    • 这就是我能想到的。很高兴这不是我的工作:)
    • 让它工作是我的工作,但旧方法需要大约 6 个小时,我需要新方法来更快地工作! :)
    • @Fritz 抱歉,第一条评论是讽刺吗?如果是的话,我会很乐意展示更多,尽管你的问题让我觉得你已经知道了很多。我在 5 年前就这样做了(显然是为了提高性能)。如果它不乐意提供帮助。
    • @thekashyap:不,不是讽刺!更像是在拍我的额头!
    【解决方案4】:

    好吧,好的,标题是可变长度的,但你必须从某个地方开始。如果你必须先读入整个文件,它可能会有点混乱。整个文件可以由一个包含标题的结构表示,直到某个长度描述符,然后是一个字节数组 - 你可以从那里开始。一旦有了标头长度,就可以将指针/长度设置为标头条目数组,然后对其进行迭代,然后为文件内容结构数组设置指针/长度,依此类推..

    可能需要打包所有各种结构数组?

    恶心。我不太喜欢我自己的设计:(

    除了重写“单独的程序”以使用数据库或 XML 之类的东西之外,任何人有更好的主意吗?

    【讨论】:

      猜你喜欢
      • 2020-05-06
      • 2017-09-15
      • 2011-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-24
      • 2017-11-29
      相关资源
      最近更新 更多