从 Python 中的二进制文件中提取特定字节
Extract specific bytes from a binary file in Python
我有非常大的二进制文件,其中包含用于 y 个传感器的 x 个 int16 数据点,以及带有一些基本信息的 headers。二进制文件被写为每个采样时间的 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)]
我用一个较小的 30 Mb 文件对此进行了测试,该文件只有 16 个传感器和 30 秒的数据。原始代码是 160 秒,mmap
是 105 秒,np.fromfile
和子采样是 0.33 秒。
剩下的问题是 - 显然使用 numpy.fromfile()
对小文件更好,但是对于可能高达 20 Gb、数小时或数天的数据以及多达 500 个传感器的更大文件是否会有问题?
我一定会试试 mmap()
:
https://docs.python.org/2/library/mmap.html
如果你正在调用 seek()
和 read()
,那么你正在阅读很多包含很多 system call overhead 的小部分 int16
.
我写了一个小测试来演示:
#!/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 只是验证两个版本做同样的事情。)
查看每个版本的第 2 个结果,mmap()
是实际时间的 ~1/3。用户时间约为 1/2,系统时间约为 1/5。
您可能加快速度的其他选择是:
(1) 如您所说,加载整个文件。大 I/O 而不是小 I/O 可以 加快速度。但是,如果超出系统内存,您将退回到分页,这比 mmap()
更糟糕(因为您必须分页)。我在这里不是很有希望,因为 mmap
已经在使用更大的 I/O。
(2) 并发。 也许 通过多个线程并行读取文件可以加快速度,但是你会 Python GIL to deal with. Multiprocessing 会通过避免 GIL 来更好地工作,而且你可以轻松地将您的数据传回顶级处理程序。然而,这将对下一个项目,地点起作用:你可能会让你的 I/O 更多 运行dom.
(3) 地点。以某种方式组织您的数据(或排序您的阅读),以便您的数据更接近。 mmap()
根据系统页面大小将文件分页:
>>> import mmap
>>> mmap.PAGESIZE
4096
>>> mmap.ALLOCATIONGRANULARITY
4096
>>>
如果您的数据靠得更近(在 4k 块内),则它已经加载到缓冲区缓存中。
(4) 更好的硬件。像固态硬盘。
我在 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}
附录:
好奇心战胜了我,我尝试了 multiprocessing
。我需要更仔细地查看我的分区,但解包次数 (53687092) 在整个试验中是相同的:
$ 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)
我有非常大的二进制文件,其中包含用于 y 个传感器的 x 个 int16 数据点,以及带有一些基本信息的 headers。二进制文件被写为每个采样时间的 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)]
我用一个较小的 30 Mb 文件对此进行了测试,该文件只有 16 个传感器和 30 秒的数据。原始代码是 160 秒,mmap
是 105 秒,np.fromfile
和子采样是 0.33 秒。
剩下的问题是 - 显然使用 numpy.fromfile()
对小文件更好,但是对于可能高达 20 Gb、数小时或数天的数据以及多达 500 个传感器的更大文件是否会有问题?
我一定会试试 mmap()
:
https://docs.python.org/2/library/mmap.html
如果你正在调用 seek()
和 read()
,那么你正在阅读很多包含很多 system call overhead 的小部分 int16
.
我写了一个小测试来演示:
#!/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 只是验证两个版本做同样的事情。)
查看每个版本的第 2 个结果,mmap()
是实际时间的 ~1/3。用户时间约为 1/2,系统时间约为 1/5。
您可能加快速度的其他选择是:
(1) 如您所说,加载整个文件。大 I/O 而不是小 I/O 可以 加快速度。但是,如果超出系统内存,您将退回到分页,这比 mmap()
更糟糕(因为您必须分页)。我在这里不是很有希望,因为 mmap
已经在使用更大的 I/O。
(2) 并发。 也许 通过多个线程并行读取文件可以加快速度,但是你会 Python GIL to deal with. Multiprocessing 会通过避免 GIL 来更好地工作,而且你可以轻松地将您的数据传回顶级处理程序。然而,这将对下一个项目,地点起作用:你可能会让你的 I/O 更多 运行dom.
(3) 地点。以某种方式组织您的数据(或排序您的阅读),以便您的数据更接近。 mmap()
根据系统页面大小将文件分页:
>>> import mmap
>>> mmap.PAGESIZE
4096
>>> mmap.ALLOCATIONGRANULARITY
4096
>>>
如果您的数据靠得更近(在 4k 块内),则它已经加载到缓冲区缓存中。
(4) 更好的硬件。像固态硬盘。
我在 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}
附录:
好奇心战胜了我,我尝试了 multiprocessing
。我需要更仔细地查看我的分区,但解包次数 (53687092) 在整个试验中是相同的:
$ 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)