Эффективный способ создания массивов Numpy из двоичных файлов - PullRequest
14 голосов
/ 27 сентября 2011

У меня очень большие наборы данных, которые хранятся в двоичных файлах на жестком диске.Вот пример структуры файла:

Заголовок файла

149 Byte ASCII Header

Начало записи

4 Byte Int - Record Timestamp

Начало семпла

2 Byte Int - Data Stream 1 Sample
2 Byte Int - Data Stream 2 Sample
2 Byte Int - Data Stream 3 Sample
2 Byte Int - Data Stream 4 Sample

Конец семпла

Имеется 122 880 семплов на запись и 713 записей на файл.Это дает общий размер 700 910 521 байт.Частота дискретизации и количество записей иногда меняются, поэтому мне приходится кодировать для определения количества каждого файла.

В настоящее время код, который я использую для импорта этих данных в массивы, работает следующим образом:

from time import clock
from numpy import zeros , int16 , int32 , hstack , array , savez
from struct import unpack
from os.path import getsize

start_time = clock()
file_size = getsize(input_file)

with open(input_file,'rb') as openfile:
  input_data = openfile.read()

header = input_data[:149]
record_size = int(header[23:31])
number_of_records = ( file_size - 149 ) / record_size
sample_rate = ( ( record_size - 4 ) / 4 ) / 2

time_series = zeros(0,dtype=int32)
t_series = zeros(0,dtype=int16)
x_series = zeros(0,dtype=int16)
y_series = zeros(0,dtype=int16)
z_series = zeros(0,dtype=int16)

for record in xrange(number_of_records):

  time_stamp = array( unpack( '<l' , input_data[ 149 + (record * record_size) : 149 + (record * record_size) + 4 ] ) , dtype = int32 )
  unpacked_record = unpack( '<' + str(sample_rate * 4) + 'h' , input_data[ 149 + (record * record_size) + 4 : 149 + ( (record + 1) * record_size ) ] ) 

  record_t = zeros(sample_rate , dtype=int16)
  record_x = zeros(sample_rate , dtype=int16)
  record_y = zeros(sample_rate , dtype=int16)
  record_z = zeros(sample_rate , dtype=int16)

  for sample in xrange(sample_rate):

    record_t[sample] = unpacked_record[ ( sample * 4 ) + 0 ]
    record_x[sample] = unpacked_record[ ( sample * 4 ) + 1 ]
    record_y[sample] = unpacked_record[ ( sample * 4 ) + 2 ]
    record_z[sample] = unpacked_record[ ( sample * 4 ) + 3 ]

  time_series = hstack ( ( time_series , time_stamp ) )
  t_series = hstack ( ( t_series , record_t ) )
  x_series = hstack ( ( x_series , record_x ) )
  y_series = hstack ( ( y_series , record_y ) )
  z_series = hstack ( ( z_series , record_z ) )

savez(output_file, t=t_series , x=x_series ,y=y_series, z=z_series, time=time_series)
end_time = clock()
print 'Total Time',end_time - start_time,'seconds'

В настоящее время это занимает около 250 секунд на файл 700 МБ, что мне кажется очень высоким.Есть ли более эффективный способ, которым я мог бы сделать это?

Окончательное решение

Использование метода numpy fromfile с настраиваемым dtype сокращает время выполнения до 9 секунд, в 27 раз быстрее, чем исходный код выше.Окончательный код приведен ниже.

from numpy import savez, dtype , fromfile 
from os.path import getsize
from time import clock

start_time = clock()
file_size = getsize(input_file)

openfile = open(input_file,'rb')
header = openfile.read(149)
record_size = int(header[23:31])
number_of_records = ( file_size - 149 ) / record_size
sample_rate = ( ( record_size - 4 ) / 4 ) / 2

record_dtype = dtype( [ ( 'timestamp' , '<i4' ) , ( 'samples' , '<i2' , ( sample_rate , 4 ) ) ] )

data = fromfile(openfile , dtype = record_dtype , count = number_of_records )
time_series = data['timestamp']
t_series = data['samples'][:,:,0].ravel()
x_series = data['samples'][:,:,1].ravel()
y_series = data['samples'][:,:,2].ravel()
z_series = data['samples'][:,:,3].ravel()

savez(output_file, t=t_series , x=x_series ,y=y_series, z=z_series, fid=time_series)

end_time = clock()

print 'It took',end_time - start_time,'seconds'

Ответы [ 4 ]

15 голосов
/ 28 сентября 2011

Некоторые подсказки:

Примерно так (не проверено, но вы поняли):

import numpy as np

file = open(input_file, 'rb')
header = file.read(149)

# ... parse the header as you did ...

record_dtype = np.dtype([
    ('timestamp', '
2 голосов
/ 27 сентября 2011

Одной явной неэффективностью является использование hstack в цикле:

  time_series = hstack ( ( time_series , time_stamp ) )
  t_series = hstack ( ( t_series , record_t ) )
  x_series = hstack ( ( x_series , record_x ) )
  y_series = hstack ( ( y_series , record_y ) )
  z_series = hstack ( ( z_series , record_z ) )

На каждой итерации это выделяет немного больший массив для каждой серии и копирует все данные, считанные до сих пор, вЭто.Это включает много ненужного копирования и может потенциально привести к неправильной фрагментации памяти.

Я бы накапливал значения time_stamp в списке и делал бы один hstack в конце,и сделал бы то же самое для record_t и т. д.

Если это не принесло бы достаточного улучшения производительности, я бы закомментировал тело цикла и начал бы возвращать вещи за один раз, чтобыПосмотрите, где именно проводится время.

2 голосов
/ 27 сентября 2011

Numpy поддерживает отображение двоичных данных из данных непосредственно в массив, как объекты через numpy.memmap . Возможно, вы сможете запомнить файл и извлечь необходимые данные с помощью смещений.

Для правильности порядка байтов просто используйте numpy.byteswap в том, что вы прочитали. Вы можете использовать условное выражение для проверки порядка в хост-системе:

if struct.pack('=f', np.pi) == struct.pack('>f', np.pi):
  # Host is big-endian, in-place conversion
  arrayName.byteswap(True)
0 голосов
/ 27 сентября 2011

Я получил удовлетворительные результаты с похожей проблемой (многоканальные файлы двоичных данных с несколькими разрешениями), используя array и struct.unpack. В моей задаче я хотел получить непрерывные данные для каждого канала, но файл имел структуру, ориентированную на интервалы, а не структуру, ориентированную на каналы.

«Секрет» заключается в том, чтобы сначала прочитать весь файл, а только потом распределять фрагменты известного размера в нужные контейнеры (в приведенном ниже коде self.channel_content[channel]['recording'] является объектом типа array):

f = open(somefilename, 'rb')    
fullsamples = array('h')
fullsamples.fromfile(f, os.path.getsize(wholefilename)/2 - 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

Конечно, я не могу утверждать, что это лучше или быстрее, чем другие ответы, но по крайней мере это то, что вы могли бы оценить.

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...