【问题标题】:Fastest way to read a binary file with a defined format?读取具有定义格式的二进制文件的最快方法?
【发布时间】:2017-07-05 18:36:08
【问题描述】:

我有具有预定义格式的大型二进制数据文件,最初由 Fortran 程序编写为小端序。我想以最快、最有效的方式阅读这些文件,所以使用array 包似乎正合我意,正如Improve speed of reading and converting from binary file? 中所建议的那样。

问题是预定义的格式是非同质的。它看起来像这样: ['<2i','<5d','<2i','<d','<i','<3d','<2i','<3d','<i','<d','<i','<3d']

每个整数 i 占用 4 个字节,每个双精度数 d 占用 8 个字节。

有没有办法我仍然可以使用超级高效的array 包(或其他建议)但格式正确?

【问题讨论】:

  • 什么是“大型二进制数据文件”?您的意思是,在您的原始文件中,所有数据的格式都不相同?你怎么知道每一个的格式?
  • 二进制文件包含大约 1000 到 1,000,000 行数据,每行数据来自物理模拟。出于测试目的,我使用的文件仅包含大约 40,000 行数据。我知道每个数据的格式,因为我有原始的 Fortran 代码,并且可以查看每个数据在内存中的类型和大小。
  • 什么是 '<3d'? When you say *"... each double d` 占用 8 个字节"*,“双 d”是什么意思?每个 d 是 4 个字节吗?请澄清。
  • 二进制文件中的“行”数据是什么意思?该文件本质上是结构的集合,每个结构都采用预定义的格式,如您的问题所示?

标签: python arrays performance binaryfiles


【解决方案1】:

使用struct。特别是struct.unpack

result = struct.unpack("<2i5d...", buffer)

这里buffer 保存给定的二进制数据。

【讨论】:

  • 我看了一下使用结构,这就是我最初依赖的,但我读到数组包更快。我误导了吗??
  • @boof, array.array 包含一系列相同类型的元素,所以用它来一次性处理structured 的数据是不可能的。
【解决方案2】:

从您的问题中不清楚您是关心实际文件读取速度(以及在内存中构建数据结构),还是关心以后的数据处理速度。

如果您只读取一次,稍后进行大量处理,您可以逐条读取文件记录(如果您的二进制数据是具有相同格式的重复记录的记录集),用struct.unpack解析它并将其附加到一个[double] 数组:

from functools import partial

data = array.array('d')
record_size_in_bytes = 9*4 + 16*8   # 9 ints + 16 doubles

with open('input', 'rb') as fin:
    for record in iter(partial(fin.read, record_size_in_bytes), b''):
        values = struct.unpack("<2i5d...", record)
        data.extend(values)

假设您可以将所有ints 转换为doubles 并且愿意接受增加分配的内存大小(问题记录中增加了 22%)。

如果您多次从文件中读取数据,则值得将所有内容转换为 doubles 中的一个大 array(如上),然后将其写回另一个文件,以后可以从中读取array.fromfile():

data = array.array('d')
with open('preprocessed', 'rb') as fin:
    n = os.fstat(fin.fileno()).st_size // 8
    data.fromfile(fin, n)

更新。感谢benchmark by @martineau,现在我们知道了一个事实,即预处理数据并将其转换为同质的双精度数组可确保从文件(使用array.fromfile())加载此类数据比读取它记录更快~20x to ~40x -每条记录,解包并附加到array(如上面的第一个代码清单所示)。

@martineau 的答案中逐记录读取的更快(和更标准)变体附加到 list 并且不会向上转换到 double 仅比 ~6x to ~10xarray.fromfile() 方法和似乎是一个更好的参考基准。

【讨论】:

  • 将所有ints 转换为doubles 会增加所需的内存量,如果文件很大并且不能一次处理一行,这可能很重要——虽然我不确定二进制文件中的“行”是什么。
  • 在这种情况下增加了 22%,是的。另外,在第二个想法中,我将 OP 中的“线条”解释得太字面了。假设一个固定大小的记录和一个文件作为此类记录的序列(非换行符分隔)更有意义。我已经编辑了我的答案以反映这一点。
  • 按照@ForceBru 的建议,除了使用更多内存并且可能需要预处理步骤之外,您使用array 的方法not 似乎比使用struct.upack() 更快.详情请参阅answer I posted
  • 但是您没有在using_preprocessed_file 中使用预处理文件。你能解决它吗,我真的很好奇array 是否像 OP 所说的那样高效。
  • 你说得对,我相信这是 OP 的初始方法。我已经更新了我的更新。 :)
【解决方案3】:

重大更新:修改为使用正确的代码读取预处理的数组文件(下面的函数using_preprocessed_file()),这极大地改变了结果。

为了确定 Python 中哪种方法更快(仅使用内置函数和标准库),我创建了一个脚本来对可用于执行此操作的不同技术进行基准测试(通过 timeit)。这有点偏长,所以为了避免分心,我只发布经过测试的代码和相关结果。 (如果对该方法有足够的兴趣,我会发布整个脚本。)

这里是比较的代码的 sn-ps:

@TESTCASE('Read and constuct piecemeal with struct')
def read_file_piecemeal():
    structures = []
    with open(test_filenames[0], 'rb') as inp:
        size = fmt1.size
        while True:
            buffer = inp.read(size)
            if len(buffer) != size:  # EOF?
                break
            structures.append(fmt1.unpack(buffer))
    return structures

@TESTCASE('Read all-at-once, then slice and struct')
def read_entire_file():
    offset, unpack, size = 0, fmt1.unpack, fmt1.size
    structures = []
    with open(test_filenames[0], 'rb') as inp:
        buffer = inp.read()  # read entire file
        while True:
            chunk = buffer[offset: offset+size]
            if len(chunk) != size:  # EOF?
                break
            structures.append(unpack(chunk))
            offset += size

    return structures

@TESTCASE('Convert to array (@randomir part 1)')
def convert_to_array():
    data = array.array('d')
    record_size_in_bytes = 9*4 + 16*8   # 9 ints + 16 doubles (standard sizes)

    with open(test_filenames[0], 'rb') as fin:
        for record in iter(partial(fin.read, record_size_in_bytes), b''):
            values = struct.unpack("<2i5d2idi3d2i3didi3d", record)
            data.extend(values)

    return data

@TESTCASE('Read array file (@randomir part 2)', setup='create_preprocessed_file')
def using_preprocessed_file():
    data = array.array('d')
    with open(test_filenames[1], 'rb') as fin:
        n = os.fstat(fin.fileno()).st_size // 8
        data.fromfile(fin, n)
    return data

def create_preprocessed_file():
    """ Save array created by convert_to_array() into a separate test file. """
    test_filename = test_filenames[1]
    if not os.path.isfile(test_filename):  # doesn't already exist?
        data = convert_to_array()
        with open(test_filename, 'wb') as file:
            data.tofile(file)

这是在我的系统上运行它们的结果:

Fastest to slowest execution speeds using Python 3.6.1
(10 executions, best of 3 repetitions)
Size of structure: 164
Number of structures in test file: 40,000
file size: 6,560,000 bytes

     Read array file (@randomir part 2): 0.06430 secs, relative  1.00x (   0.00% slower)
Read all-at-once, then slice and struct: 0.39634 secs, relative  6.16x ( 516.36% slower)
Read and constuct piecemeal with struct: 0.43283 secs, relative  6.73x ( 573.09% slower)
    Convert to array (@randomir part 1): 1.38310 secs, relative 21.51x (2050.87% slower)

有趣的是,大多数 sn-ps 实际上在 Python 2 中更快...

Fastest to slowest execution speeds using Python 2.7.13
(10 executions, best of 3 repetitions)
Size of structure: 164
Number of structures in test file: 40,000
file size: 6,560,000 bytes

     Read array file (@randomir part 2): 0.03586 secs, relative  1.00x (   0.00% slower)
Read all-at-once, then slice and struct: 0.27871 secs, relative  7.77x ( 677.17% slower)
Read and constuct piecemeal with struct: 0.40804 secs, relative 11.38x (1037.81% slower)
    Convert to array (@randomir part 1): 1.45830 secs, relative 40.66x (3966.41% slower)

【讨论】:

  • 从发布的代码看来,您实际上没有在最终测试用例中使用预处理文件(完整的文件应使用array.fromfile() 读取)。
  • @randomir:你说的完全正确——我很抱歉这个错误(基准开发的产物)。我的答案已相应更新...恭喜! ;-)
  • 我碰巧了。 :) 但是,结果确实很有趣。感谢您进行此基准测试。
  • 什么是fmt1
  • @大卫:fmt1 = struct.Struct('&lt;2i5d2idi3d2i3didi3d')
【解决方案4】:

查看numpyfromfile 函数的文档:https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.fromfile.htmlhttps://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html#arrays-dtypes-constructing

最简单的例子:

import numpy as np
data = np.fromfile('binary_file', dtype=np.dtype('<i8, ...'))

numpy 中阅读有关“结构化数组”的更多信息以及如何在此处指定其数据类型:https://docs.scipy.org/doc/numpy/user/basics.rec.html#

【讨论】:

    【解决方案5】:

    这里有很多好的和有用的答案,但我认为最好的解决方案需要更多解释。我实现了一种方法,该方法使用内置的read() 一次性读取整个数据文件,同时构造一个numpy ndarray。这比单独读取数据和构造数组效率更高,但也有点挑剔。

    line_cols = 20              #For example
    line_rows = 40000           #For example
    data_fmt = 15*'f8,'+5*'f4,' #For example (15 8-byte doubles + 5 4-byte floats)
    data_bsize = 15*8 + 4*5     #For example
    with open(filename,'rb') as f:
            data = np.ndarray(shape=(1,line_rows),
                              dtype=np.dtype(data_fmt),
                              buffer=f.read(line_rows*data_bsize))[0].astype(line_cols*'f8,').view(dtype='f8').reshape(line_rows,line_cols)[:,:-1]
    

    在这里,我们使用open 中的'rb' 选项将文件作为二进制文件打开。然后,我们用适当的形状和 dtype 构造我们的ndarray 以适应我们的读取缓冲区。然后我们将ndarray 减少为一个一维数组,方法是采用它的第零个索引,我们所有的数据都隐藏在这个数组中。然后,我们使用np.astypenp.viewnp.reshape 方法重塑数组。这是因为 np.reshape 不喜欢混合 dtype 的数据,我可以将整数表示为双精度数。

    这种方法比在数据中逐行循环快约 100 倍,并且有可能被压缩成一行代码。

    将来,我可能会尝试使用Fortran 脚本更快地读取数据,该脚本基本上将二进制文件转换为文本文件。我不知道这是否会更快,但可能值得一试。

    【讨论】:

      猜你喜欢
      • 2012-05-01
      • 2011-01-03
      • 2013-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-02
      • 2011-01-24
      • 2017-10-25
      相关资源
      最近更新 更多