提高从二进制文件读取和转换的速度?

时间:2011-04-27 12:24:56

标签: python performance file-io

我知道之前有一些关于文件读取,二进制数据处理和使用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']))])

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

我想知道的是:

  1. 如果存在违反“良好做法”的行为:错误安排的循环,重复操作需要的时间超过必要时间,使用效率低下的容器类型(字典?)等。
  2. 如果此读取速度正常,或者与Python正常,并且读取速度
  3. 如果创建C ++编译的扩展可能会提高性能,并且如果它是推荐的方法。
  4. (当然)如果有人建议对此代码进行一些修改,最好根据之前的类似操作经验。
  5. 感谢您阅读

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

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

    修改:我目前正在讨论Sebastian建议使用array fromfile()方法,并很快将最终代码放在此处。此外,每一个结果对我都非常有用,我非常高兴地感谢所有善意回答的人。

    最终形式与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,速度提升非常令人印象深刻。

5 个答案:

答案 0 :(得分:15)

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

import array
import os

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

@samplebias's answerstruct.unpack快40倍。

答案 1 :(得分:7)

如果文件只有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倍。

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

extend() acepts iterables,即代替.extend([...]),您可以编写.extend(...)。它可能会加速程序,因为 extend()将在生成器上处理,不再在构建列表上处理

您的代码存在不一致:您先定义channel_content = {},然后执行channel_content[channel]['recording'].extend(...),需要初步存在密钥频道和子密钥< strong>'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要读取的值?

我在文档中读到:

  

class struct.Struct(format)

     

返回a   写入和的新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建议使用数组

答案 4 :(得分:0)

来自文件调用的单个数组肯定是最快的,但如果数据与其他值类型交错,则无法工作。

在这种情况下,可以与之前的struct回答相结合的另一个大速度增加是,不是多次调用unpack函数,而是使用每个chunk的格式预编译struct.Struct对象。来自docs

  

创建一个Struct对象并调用其方法更多   比以后用相同的格式调用struct函数更有效   格式字符串只需要编译一次。

例如,如果你想一次解包1000个交错短裤和浮点数,你可以写:

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

(请注意,该示例仅是部分示例,需要考虑更改文件末尾的chunksize并打破while循环。)