用Python从二进制文件中提取特定字节

用Python从二进制文件中提取特定字节,python,numpy,mmap,seek,fromfile,Python,Numpy,Mmap,Seek,Fromfile,我有非常大的二进制文件,其中包含y传感器的x个int16数据点,以及包含一些基本信息的头文件。二进制文件被写为每个采样时间的y值,最多x个采样,然后是另一组读数,依此类推。如果我想要所有的数据,我使用的是numpy.fromfile(),它的工作速度非常快。但是,如果我只想要传感器数据的子集或特定传感器,我目前有一个可怕的双for循环,使用file.seek()、file.read()、和struct.unpack(),这会花费很长时间。在python中有没有其他更快的方法?也许是我不太理解的m

我有非常大的二进制文件,其中包含y传感器的x个int16数据点,以及包含一些基本信息的头文件。二进制文件被写为每个采样时间的y值,最多x个采样,然后是另一组读数,依此类推。如果我想要所有的数据,我使用的是
numpy.fromfile()
,它的工作速度非常快。但是,如果我只想要传感器数据的子集或特定传感器,我目前有一个可怕的双
for
循环,使用
file.seek()
file.read()
、和
struct.unpack()
,这会花费很长时间。在python中有没有其他更快的方法?也许是我不太理解的
mmap()
?或者只是使用整个
fromfile()
然后再进行二次采样

data = numpy.empty(num_pts, sensor_indices)
for i in range(num_pts):
    for j in range(sensor_indices):
        curr_file.seek(bin_offsets[j])
        data_binary = curr_file.read(2)
        data[j][i] = struct.unpack('h', data_binary)[0]
在遵循@rrauenza关于
mmap
的建议后,我将代码编辑为

mm = mmap.mmap(curr_file.fileno(), 0, access=mmap.ACCESS_READ)
data = numpy.empty(num_pts,sensor_indices)
for i in range(num_pts):
    for j in range(len(sensor_indices)):
        offset += bin_offsets[j] * 2
        data[j][i] = struct.unpack('h', mm[offset:offset+2])[0]
虽然这比以前快了,但仍然比以前慢了几个数量级

shape = (x, y)
data = np.fromfile(file=self.curr_file, dtype=np.int16).reshape(shape)
data = data.transpose()
data = data[sensor_indices, :]
data = data[:, range(num_pts)]
我用一个较小的30MB文件进行了测试,该文件只有16个传感器,数据量为30秒。原始编码为160秒,
mmap
为105秒,
np.fromfile
和子采样为0.33秒


剩下的问题是-显然,使用
numpy.fromfile()
处理小文件会更好,但对于大得多的文件来说,会有问题吗?这些文件可能长达20 Gb,包含数小时或数天的数据,最多500个传感器?

我肯定会尝试
mmap()

您正在读取大量的小数据位,如果您正在为要提取的每个
int16
调用
seek()
read()
,这些数据位会产生大量的错误

我编写了一个小测试来演示:

#!/usr/bin/python

import mmap
import os
import struct
import sys

FILE = "/opt/tmp/random"  # dd if=/dev/random of=/tmp/random bs=1024k count=1024
SIZE = os.stat(FILE).st_size
BYTES = 2
SKIP = 10


def byfile():
    sum = 0
    with open(FILE, "r") as fd:
        for offset in range(0, SIZE/BYTES, SKIP*BYTES):
            fd.seek(offset)
            data = fd.read(BYTES)
            sum += struct.unpack('h', data)[0]
    return sum


def bymmap():
    sum = 0
    with open(FILE, "r") as fd:
        mm = mmap.mmap(fd.fileno(), 0, prot=mmap.PROT_READ)
        for offset in range(0, SIZE/BYTES, SKIP*BYTES):
            data = mm[offset:offset+BYTES]
            sum += struct.unpack('h', data)[0]
    return sum


if sys.argv[1] == 'mmap':
    print bymmap()

if sys.argv[1] == 'file':
    print byfile()
我运行了两次每个方法来补偿缓存。我使用
time
是因为我想测量
user
sys
时间

结果如下:

[centos7:/tmp]$ time ./test.py file
-211990391

real    0m44.656s
user    0m35.978s
sys     0m8.697s
[centos7:/tmp]$ time ./test.py file
-211990391

real    0m43.091s
user    0m37.571s
sys     0m5.539s
[centos7:/tmp]$ time ./test.py mmap
-211990391

real    0m16.712s
user    0m15.495s
sys     0m1.227s
[centos7:/tmp]$ time ./test.py mmap
-211990391

real    0m16.942s
user    0m15.846s
sys     0m1.104s
[centos7:/tmp]$ 
(总和-211990391只是验证两个版本是否执行相同的操作。)

查看每个版本的第二个结果,
mmap()
约为实时结果的1/3。用户时间约为1/2,系统时间约为1/5

您的其他加速选择包括:

(1) 如前所述,加载整个文件。大I/O而不是小I/O可以加快速度。但是,如果超出系统内存,您将返回分页,这将比
mmap()
(因为您必须分页)更糟糕。我在这里不是特别有希望,因为
mmap
已经在使用更大的I/O了

(2) 并发性。也许通过多个线程并行读取文件可以加快速度,但是您需要处理Python。避免GIL将更好地工作,并且您可以轻松地将数据传递回顶级处理程序。然而,这将对下一项——位置——起作用:您可能会使I/O更随机

(3) 地点。以某种方式组织您的数据(或排列您的读取顺序),使您的数据更加紧密
mmap()
根据系统页面大小将文件分块分页:

>>> import mmap
>>> mmap.PAGESIZE
4096
>>> mmap.ALLOCATIONGRANULARITY
4096
>>> 
如果您的数据距离较近(在4k区块内),它将已经加载到缓冲区缓存中

(4) 更好的硬件。就像SSD一样

我在SSD上运行了这个,速度快得多。我运行了python的概要文件,想知道解包是否昂贵。这不是:

$ python -m cProfile test.py mmap                                                                                                                        
121679286
         26843553 function calls in 8.369 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    6.204    6.204    8.357    8.357 test.py:24(bymmap)
        1    0.012    0.012    8.369    8.369 test.py:3(<module>)
 26843546    1.700    0.000    1.700    0.000 {_struct.unpack}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'fileno' of 'file' objects}
        1    0.000    0.000    0.000    0.000 {open}
        1    0.000    0.000    0.000    0.000 {posix.stat}
        1    0.453    0.453    0.453    0.453 {range}
代码:


你调查过熊猫吗?这是伟大的排序大数据集在几乎任何你想要的方式。嗨,欢迎来到StackOverflow!你的标题有点混乱——strip通常意味着删除。一个更准确的词可能是extract。您的超大文件有多大?我认为这实际上取决于您拥有的RAM数量。这是个问题
mmap()
应该随着数据的增长而线性扩展,
numpy.fromfile()
不会随着数据大小的增长而扩展,因为在某个时候您需要翻页。您可以使用
numpy.memmap
并充分利用这两个世界。在建立索引之前进行切片(
0:num\u pts
),以最小化副本。
$ time ./test2.py 4
[(4415068.0, 13421773), (-145566705.0, 13421773), (14296671.0, 13421773), (109804332.0, 13421773)]
(-17050634.0, 53687092)

real    0m5.629s
user    0m17.756s
sys     0m0.066s
$ time ./test2.py 1
[(264140374.0, 53687092)]
(264140374.0, 53687092)

real    0m13.246s
user    0m13.175s
sys     0m0.060s
#!/usr/bin/python

import functools
import multiprocessing
import mmap
import os
import struct
import sys

FILE = "/tmp/random"  # dd if=/dev/random of=/tmp/random bs=1024k count=1024
SIZE = os.stat(FILE).st_size
BYTES = 2
SKIP = 10


def bymmap(poolsize, n):
    partition = SIZE/poolsize
    initial = n * partition
    end = initial + partition
    sum = 0.0
    unpacks = 0
    with open(FILE, "r") as fd:
        mm = mmap.mmap(fd.fileno(), 0, prot=mmap.PROT_READ)
        for offset in xrange(initial, end, SKIP*BYTES):
            data = mm[offset:offset+BYTES]
            sum += struct.unpack('h', data)[0]
            unpacks += 1
    return (sum, unpacks)


poolsize = int(sys.argv[1])
pool = multiprocessing.Pool(poolsize)
results = pool.map(functools.partial(bymmap, poolsize), range(0, poolsize))
print results
print reduce(lambda x, y: (x[0] + y[0], x[1] + y[1]), results)