快速阅读和解释二进制文件

时间:2017-10-11 15:05:50

标签: python performance binary

我有一个巨大的二进制文件(几GB),它有以下数据格式:

4个后续字节形成一个复合数据点(32位),包括:

b0-b3    4 flag bits
b4-b17  14 bit signed integer
b18-b32 14 bit signed integer

我需要分别访问有符号整数和标志位,并附加到列表或更智能的数据结构(尚未确定)。目前我正在使用以下代码阅读:

from collections import namedtuple
DataPackage = namedtuple('DataPackage', ['ie', 'if1', 'if2', 'if3', 'quad2', 'quad1'])
def _unpack_integer(bits):
    value = int(bits, 2)
    if bits[0] == '1':
        value -= (1 << len(bits))
    return value


def unpack(data):
    bits = ''.join(['{0:08b}'.format(b) for b in bytearray(data)])
    flags = [bool(bits[i]) for i in range(4)]
    quad2 = _unpack_integer(bits[4:18])
    quad1 = _unpack_integer(bits[18:])
    return DataPackage(flags[0], flags[1], flags[2], flags[3], quad2, quad1)

def read_file(filename, datapoints=None):
    data = []
    i = 0
    with open(filename, 'rb') as fh:
        value = fh.read(4)
        while value:
            dp = unpack(value)
            data.append(dp)
            value = fh.read(4)
            i += 1
            if i % 10000 == 0:
                print('Read: %d kB' % (float(i) * 4.0 / 1000.0))
            if datapoints:
                if i == datapoints:
                    break
    return data

if __name__ == '__main__':
    data = read_heterodyne_file('test.dat')

此代码有效,但对于我的目的来说它太慢了(对于100k数据点,每个4字节为2s)。至少我需要10倍的速度因子。

分析器说代码花费的时间主要是字符串格式化(获取位)和_unpack_integer()。

不幸的是我不知道如何继续这里。我正在考虑使用cython或直接编写一些c代码来进行读取。我也尝试过Pypy ant,它给了我巨大的性能提升但不幸的是它需要与更大的项目兼容与Pypy合作。

2 个答案:

答案 0 :(得分:1)

如果您已经有一个识别数据结构的c / c ++库,我建议您尝试ctypes。好处是,数据结构仍然可用于你的python,而'loading'会很快。如果您已经有一个c库来加载数据,您可以使用该库中的函数调用来完成繁重的工作,并将数据映射到您的python结构中。对不起,我将无法尝试为您的示例提供正确的代码(可能是其他人的手杖),但这里有一些提示可以帮助您入门

我对如何在python中创建位向量的看法: https://stackoverflow.com/a/40364970/262108

我上面提到的方法,我应用于你描述的类似问题。在这里,我使用ctypes创建一个ctypes数据结构(从而使我能够将该对象用作任何其他python对象),同时还能够将它传递给C库:

https://gist.github.com/lonetwin/2bfdd41da41dae326afb

答案 1 :(得分:1)

由于Jean-FrançoisFabre的暗示,我发现了一个使用位掩码的合适的方法,与问题中的代码相比,这使得我的速度提高了6倍。它现在拥有大约300k的数据点/秒。

我也忽略了使用公认的好名字元组并将其替换为列表,因为我发现这也是一个瓶颈。

代码现在看起来像

masks = [2**(31-i) for i in range(4)]
def unpack3(data):
    data = struct.unpack('>I', data)[0]
    quad2 = (data & 0xfffc000) >> 14
    quad1 = data & 0x3fff
    if (quad2 & (1 << (14 - 1))) != 0:
        quad2 = quad2 - (1 << 14)
    if (quad1 & (1 << (14 - 1))) != 0:
        quad1 = quad1 - (1 << 14)
    flag0 = data & masks[0]
    flag1 = data & masks[1]
    flag2 = data & masks[2]
    flag3 = data & masks[3]
    return flag0, flag1, flag2, flag3, quad2, quad1

线路剖析器说:

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    58                                           @profile
    59                                           def unpack3(data):
    60   1000000      3805727      3.8     12.3      data = struct.unpack('>I', data)[0]
    61   1000000      2670576      2.7      8.7      quad2 = (data & 0xfffc000) >> 14
    62   1000000      2257150      2.3      7.3      quad1 = data & 0x3fff
    63   1000000      2634679      2.6      8.5      if (quad2 & (1 << (14 - 1))) != 0:
    64    976874      2234091      2.3      7.2          quad2 = quad2 - (1 << 14)
    65   1000000      2660488      2.7      8.6      if (quad1 & (1 << (14 - 1))) != 0:
    66    510978      1218965      2.4      3.9          quad1 = quad1 - (1 << 14)
    67   1000000      3099397      3.1     10.0      flag0 = data & masks[0]
    68   1000000      2583991      2.6      8.4      flag1 = data & masks[1]
    69   1000000      2486619      2.5      8.1      flag2 = data & masks[2]
    70   1000000      2473058      2.5      8.0      flag3 = data & masks[3]
    71   1000000      2742228      2.7      8.9      return flag0, flag1, flag2, flag3, quad2, quad1

因此,没有一个明确的瓶颈了。可能现在它和纯Python一样快。或者有人有进一步加速的想法吗?