Forma eficiente de crear matrices numpy a partir de archivos binarios

Tengo conjuntos de datos muy grandes que se almacenan en archivos binarios en el disco duro. Aquí hay un ejemplo de la estructura del archivo:

Encabezado de archivo

149 Byte ASCII Header 

Inicio de grabación

 4 Byte Int - Record Timestamp 

Ejemplo de inicio

 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 

Muestra final

Hay 122,880 muestras por registro y 713 registros por archivo. Esto produce un tamaño total de 700,910,521 Bytes. La frecuencia de muestreo y el número de registros varían a veces, así que tengo que codificar para detectar el número de cada uno por archivo.

Actualmente, el código que utilizo para importar estos datos en arreglos funciona así:

 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' 

Esto toma actualmente unos 250 segundos por archivo de 700 MB, lo que para mí me parece muy alto. ¿Hay una manera más eficiente de hacer esto?

Solución final

Usando el método de archivo de Npypy con un tipo de dato personalizado, reduzca el tiempo de ejecución a 9 segundos, 27 veces más rápido que el código original anterior. El código final está abajo.

 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' 

Algunos consejos:

Algo como esto (no probado, pero tienes la idea):

 importar numpy como np

 archivo = abrir (archivo de entrada, 'rb')
 header = file.read (149)

 # ... analiza el encabezado como lo hiciste ...

 record_dtype = np.dtype ([
     ('timestamp', '

Una ineficiencia evidente es el uso de hstack en un bucle:

  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 ) ) 

En cada iteración, esto asigna una matriz un poco más grande para cada una de las series y copia todos los datos leídos hasta ahora. Esto implica una gran cantidad de copias innecesarias y potencialmente puede conducir a una fragmentación de la memoria defectuosa.

Acumularía los valores de time_stamp en una lista y haría un hstack al final, y haría exactamente lo mismo para record_t etc.

Si eso no trae suficientes mejoras de rendimiento, comentaría el cuerpo del bucle y comenzaría a recuperar las cosas de una en una, para ver dónde se gasta exactamente el tiempo.