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

14 投票
5 回答
13349 浏览
提问于 2025-04-16 16:31

我知道之前有一些关于文件读取、二进制数据处理和整数转换的问题,都是用 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内存的电脑上,每读取1MB的数据大约需要2.2秒,而我的文件通常有20MB以上,这就造成了很烦人的延迟(尤其是考虑到我正在尝试模仿的另一个基准共享软件加载文件的速度快得多)。

我想知道:

  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 相比,速度提升是 非常 令人印象深刻的。

5 个回答

2

一次性从文件读取一个数组是最快的,但如果数据系列和其他类型的值交错在一起,这种方法就不适用了。

在这种情况下,还有一种可以和之前提到的结构体方法结合使用的提速方法,那就是不必多次调用解包函数,而是先创建一个结构体对象,指定每一块数据的格式。根据文档的说明:

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

举个例子,如果你想一次解包1000个交错的短整型和浮点型数据,你可以这样写:

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

(注意,这个例子只是部分代码,还需要处理文件末尾的块大小变化,并打破循环。)

7

如果文件只有20到30兆,那为什么不一次性读取整个文件,然后用一个叫unpack的函数把数字解码出来,再通过遍历数组把这些数字分配到你的通道里呢?

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

快速测试显示,这种方法在处理一个20兆的文件时,比用struct.unpack('h', f.read(2))逐个读取要快10倍。

16

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

import array
import os

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

这个方法比使用@samplebias的回答中的struct.unpack快40倍。

撰写回答