繁体   English   中英

读取具有定义格式的二进制文件的最快方法?

[英]Fastest way to read a binary file with a defined format?

我有具有预定义格式的大型二进制数据文件,最初由 Fortran 程序编写为 little endians。 我想以最快、最有效的方式读取这些文件,因此按照提高读取二进制文件和转换二进制文件的速度中的建议,使用array包似乎正合我意。 .

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

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

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

使用struct 特别是struct.unpack

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

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

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

如果您只读取一次,然后进行大量处理,则可以逐条读取文件记录(如果您的二进制数据是具有相同格式的重复记录的记录集),使用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)

假设您被允许将所有int s 转换为double s愿意接受分配内存大小的增加(问题记录增加 22%)。

如果您多次从文件中读取数据,那么将所有内容转换为一个大型double 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)

更新 多亏了@martineau 的一个很好的基准测试,现在我们知道了一个事实,即预处理数据并将其转换为同质双精度数组可确保从文件(使用array.fromfile() )加载此类数据的速度快~20x to ~40x倍而不是逐条记录地读取它,解包并附加到array (如上面的第一个代码清单所示)。

@martineau 的答案中逐条记录读取的更快(和更标准)变化附加到list并且不向上转换为double仅比array.fromfile()方法慢~6x to ~10x并且看起来像一个更好的参考基准。

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

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

以下是比较的代码片段:

@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)

有趣的是,大多数代码片段在 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)

查看numpyfromfile函数的文档: https ://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.fromfile.html 和https://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#

这里有很多好的和有用的答案,但我认为最好的解决方案需要更多解释。 我实现了一种方法,使用内置的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'选项将文件作为二进制文件open 然后,我们构建具有适当形状和数据类型的ndarray以适合我们的读取缓冲区。 然后我们通过获取第零个索引将ndarray为一维数组,我们所有的数据都隐藏在该索引中。 然后,我们使用np.astypenp.viewnp.reshape方法重塑数组。 这是因为np.reshape不喜欢混合数据类型的数据,而且我可以将整数表示为双精度数。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM