【问题标题】:Improve speed of reading and converting from binary file?提高从二进制文件读取和转换的速度?
【发布时间】:2011-08-13 19:51:13
【问题描述】:

我知道之前有一些关于使用struct 进行文件读取、二进制数据处理和整数转换的问题,所以我来这里询问我的一段代码,我认为这需要太多时间来运行。正在读取的文件是多通道数据采样记录(短整数),具有插入的数据间隔(因此嵌套了 for 语句)。代码如下:

# channel_content is a dictionary, channel_content[channel]['nsamples'] is a string
for rec in xrange(number_of_intervals)):
    for channel in channel_names:
        channel_content[channel]['recording'].extend(
            [struct.unpack( "h", f.read(2))[0]
            for iteration in xrange(int(channel_content[channel]['nsamples']))])

使用此代码,我使用具有 2Mb RAM 的双核读取每兆字节 2.2 秒,而我的文件通常有 20+ Mb,这会产生一些非常烦人的延迟(特别是考虑到我试图镜像的另一个基准共享软件程序加载文件的速度更快)。

我想知道的:

  1. 如果有一些违反“良好做法”的行为:循环安排不当、重复操作花费的时间超过必要时间、使用效率低下的容器类型(字典?)等。
  2. 如果这个读取速度正常,或者Python正常,如果读取速度正常
  3. 如果创建 C++ 编译扩展可能会提高性能,以及是否是推荐的方法。
  4. (当然)如果有人建议对此代码进行一些修改,最好基于以前的类似操作经验。

感谢阅读

(我已经发了几个关于我这个工作的问题,希望它们都是概念上无关的,也希望不要太重复。)

编辑: channel_names 是一个列表,所以我做了@eumiro 建议的更正(删除拼写错误的括号)

编辑:我目前正在接受 Sebastian 的建议,即使用 arrayfromfile() 方法,并将很快将最终代码放在这里。另外,每一次投稿都对我很有帮助,非常感谢所有热心回答的人。

使用array.fromfile() 一次后的最终形式,然后通过切片大数组为每个通道交替扩展一个数组:

fullsamples = array('h')
fullsamples.fromfile(f, os.path.getsize(f.filename)/fullsamples.itemsize - f.tell())
position = 0
for rec in xrange(int(self.header['nrecs'])):
    for channel in self.channel_labels:
        samples = int(self.channel_content[channel]['nsamples'])
        self.channel_content[channel]['recording'].extend(
                                                fullsamples[position:position+samples])
        position += samples

与一次读取文件或以任何形式使用struct相比,速度提升非常令人印象深刻。

【问题讨论】:

  • 你确定for channel in [channel_names]:?这将使用channel = channel_names 运行一个循环。另外,您从哪里获得channel_content[channel] 中的所有值?
  • 您是否尝试分析您的代码?
  • @eumiro:你是对的,不应该有括号,因为 channel_names 实际上是一个字符串列表(名称本身)。
  • @9000: 我对很多print "starting this"print "ending this" 进行了一些“业余”分析,但我承认我不确定如何正确分析,更不用说该怎么做根据分析结果提高性能。
  • 你的意思是(os.path.getsize(f.filename) - f.tell()) / fullsamples.itemsize

标签: python performance file-io


【解决方案1】:

您可以使用array 来读取您的数据:

import array
import os

fn = 'data.bin'
a = array.array('h')
a.fromfile(open(fn, 'rb'), os.path.getsize(fn) // a.itemsize)

它比 struct.unpack 快 40 倍 @samplebias's answer

【讨论】:

  • 专家不一样!我显然会测试你的建议,很快我们就会知道结果。唯一的“问题”是不应读取所有文件,仅从字节“N”到末尾,“N”是文件头中描述的某个数字。非常感谢!
  • @heltonbiker:你可以使用.fromfile()从文件中的任何地方读取,只要你给它一个文件对象(你可以在.fromfile()调用之前/之后读取它)和数字要阅读的项目。
  • 我在您的回答中投了我的票,因为array 在文档中被描述为“高效”,并且它具有fromfile() 方法。但是我想知道(因为文件中交替记录了四个间隔较短的通道)是否一次读取所有文件然后将大数组切片到正确的通道会比初始化四个数组并直接读取正确的部分更好。 .现在我要出去吃午饭,但很快我就会发布结果。再次感谢,也感谢所有做出贡献的人。
  • @heltonbiker:array.array() 支持切片,您可以多次调用.fromfile()。您可以尝试任何一种方式,看看哪种方式更快。
  • 似乎多次调用.fromfile()struct 方法慢。我将尝试创建一个数组并从中切片以扩展每个通道的数组。
【解决方案2】:

如果文件只有 20-30M,为什么不读取整个文件,在一次调用 unpack 时解码 nums,然后通过遍历数组将它们分配到您的通道中:

data = open('data.bin', 'rb').read()
values = struct.unpack('%dh' % len(data)/2, data)
del data
# iterate over channels, and assign from values using indices/slices

快速测试表明,在 20M 的文件上,这比 struct.unpack('h', f.read(2)) 提高了 10 倍。

【讨论】:

  • 这看起来是一个非常好的建议,但我无法完全捕捉 struct() 参数的语法。我会看一下文档。
  • 来自struct docs:“格式字符前面可能有一个整数重复计数。例如,格式字符串'4h'的含义与'hhhh'完全相同。”
  • array.array 让您读取 20M 文件的速度提高 40 倍 stackoverflow.com/questions/5804052/…
  • @JF 当然,array.fromfile 更快。
【解决方案3】:

单个数组 fromfile 调用绝对是最快的,但如果数据序列与其他值类型交错,则不会工作。

在这种情况下,可以与前面的 struct 答案相结合的另一大速度提升是,无需多次调用 unpack 函数,而是使用每个块的格式预编译一个 struct.Struct 对象。来自docs

创建一个 Struct 对象并调用它的方法更多 比使用相同格式调用结构函数更有效,因为 格式字符串只需要编译一次。

因此,例如,如果您想一次拆开 1000 个交错的 short 和 float,您可以这样写:

chunksize = 1000
structobj = struct.Struct("hf" * chunksize)
while True:
    chunkdata = structobj.unpack(fileobj.read(structobj.size))

(请注意,该示例只是部分示例,需要考虑更改文件末尾的块大小并中断while循环。)

【讨论】:

    【解决方案4】:

    extend() 接受 iterables,也就是说你可以写 .extend([...]) 而不是 .extend(...) 。它可能会加速程序,因为 extend() 将在生成器上处理,而不是在构建列表上处理

    您的代码中存在不连贯性:您首先定义 channel_content = {} ,然后执行 channel_content[channel]['recording'].extend(...) ,这需要预先存在密钥 channel 和子密钥 'recording ' 将列表作为值,以便能够扩展到某物

    self.channel_content[channel]['nsamples'] 的本质是什么,才能提交给 int() 函数?

    number_of_intervals 来自哪里?间隔的性质是什么?

    rec in xrange(number_of_intervals)): 循环中,我再也看不到 rec 了。因此,在我看来,您重复相同的循环过程 for channel in channel_names: 的次数与 number_of_intervals 表示的数字一样多。是否有 number_of_intervals * int(self.channel_content[channel]['nsamples']) * 2 个值可以读取 f ?

    我在文档中读到:

    类 struct.Struct(格式)

    返回一个 新的 Struct 对象,它写入和 根据读取二进制数据 格式字符串格式。创建一个 构造对象一次并调用它 方法比调用更有效 结构功能相同 格式,因为只有格式字符串 需要编译一次。

    这表达了与samplebias相同的想法。

    如果您的目标是创建字典,也可以将 dict() 与生成器一起用作参数

    .

    编辑

    我提议

    channel_content = {}
    for rec in xrange(number_of_intervals)):
        for channel in channel_names:
            N = int(self.channel_content[channel]['nsamples'])
            upk = str(N)+"h", f.read(2*N)
            channel_content[channel]['recording'].extend(struct.unpack(x) for i,x in enumerate(upk) if not i%2)
    

    我不知道如何考虑 J.F. Sebastian 使用数组的建议

    【讨论】:

    • 非常欢迎您的关注,谢谢。 self.channel_content[channel]['nsamples']是从文件头的ascii字段读取的字符串,如number_of_intervals。你对channel_content 的看法是对的,那条线不应该在那里。括号放在extend() 内,因为它是一个列表理解,但现在我根据@samplebias 的建议单独创建values。我将按照您的建议研究 Struct() 对象。非常感谢。
    • 截至rec in xrange(number_of_intervals),您猜对了:rec 只是一个虚拟变量,想法是循环number_of_intervals 次,因为它得到(必须得到,根据文件格式规范)正确的文件大小。它工作正常,特别是在此处建议的所有这些修改之后。
    • 我肯定会提出他的建议,因为将数组描述为列表的“高效”替代品,加上直接从文件读取的能力,使它成为一个非常非常强大的候选者,因为塞巴斯蒂安自己说的。
    【解决方案5】:

    不确定它是否会更快,但我会尝试解码大块单词而不是一次解码一个单词。例如,您可以一次读取 100 个字节的数据,例如:

    s = f.read(100)
    struct.unpack(str(len(s)/2)+"h", s)
    

    【讨论】:

    • struct.calcsize('h') == 2 所以你应该使用len(s)/2
    猜你喜欢
    • 1970-01-01
    • 2013-10-26
    • 1970-01-01
    • 2011-12-11
    • 2016-11-23
    • 1970-01-01
    • 1970-01-01
    • 2018-03-23
    • 1970-01-01
    相关资源
    最近更新 更多