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

时间:2017-07-05 18:36:08

标签: python arrays performance binaryfiles

我有大量的二进制数据文件,它们具有预定义的格式,最初由Fortran程序编写为小端。我想以最快,最有效的方式阅读这些文件,因此使用array软件包似乎正如我所建议的那样here

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

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

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

5 个答案:

答案 0 :(得分:4)

使用struct。特别是struct.unpack

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

此处buffer包含给定的二进制数据。

答案 1 :(得分:3)

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

如果您只阅读一次,稍后进行繁重的处理,则可以按记录读取文件记录(如果您的二进制数据是具有相同格式的重复记录的记录集),则使用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转发给double 愿意接受分配内存大小的增加(您的记录增加22%)问题)。

如果您多次从文件中读取数据,将所有内容转换为array double的大data = array.array('d') with open('preprocessed', 'rb') as fin: n = os.fstat(fin.fileno()).st_size // 8 data.fromfile(fin, n) (如上所述)并将其写回另一个文件是值得的稍后可以阅读array.fromfile()

array.fromfile()

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

@ martineau的答案中记录逐个记录的更快(更标准)的变化附加到double并且不会向~6x to ~10x转播array.fromfile()慢于this.getPages方法,似乎是更好的参考基准。

答案 2 :(得分:2)

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

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

以下是比较的代码片段:

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)

有趣的是,Python 2中的大部分片段实际上都更快......

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)

答案 3 :(得分:0)

查看numpy fromfile函数的文档: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, ...'))

详细了解&#34;结构化阵列&#34;在numpy中以及如何在此处指定其数据类型:https://docs.scipy.org/doc/numpy/user/basics.rec.html#

答案 4 :(得分:0)

这里有很多好的和有用的答案,但我认为最好的解决方案需要更多的解释。我实现了一个方法,使用内置的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]

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

这种方法比通过数据循环逐行<〜>快<100倍,并且可能被压缩成一行代码。

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

相关问题