Улучшить скорость чтения и конвертации из двоичного файла? - PullRequest
12 голосов
/ 27 апреля 2011

Я знаю, что раньше были некоторые вопросы, касающиеся чтения файлов, обработки двоичных данных и целочисленного преобразования с использованием 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']))])

С этим кодом я получаю 2,2 секунды на мегабайт для чтения с двухъядерным процессором с 2 МБ ОЗУ, и мои файлы обычно имеют 20+ МБ, что дает некоторую весьма раздражающую задержку (особенно если учесть еще одну условно-бесплатную программу, которую я пытаюсь отразить загружает файл ПУТЬ быстрее).

Что бы я хотел знать:

  1. Если есть какое-то нарушение «хорошей практики»: неправильно организованные циклы, повторяющиеся операции, которые занимают больше времени, чем необходимо, использование неэффективных типов контейнеров (словарей?) И т. Д.
  2. Если эта скорость чтения нормальная или нормальная к Python, и если скорость чтения
  3. Если создание скомпилированного расширения C ++ может повысить производительность, и если это будет рекомендуемый подход.
  4. (конечно) Если кто-то предлагает какую-либо модификацию этого кода, желательно на основе предыдущего опыта с аналогичными операциями.

Спасибо за чтение

(Я уже отправил несколько вопросов об этой моей работе, надеюсь, они все концептуально не связаны, и я также надеюсь, что не буду слишком повторяющимся.)

Редактировать: channel_names - список, поэтому я сделал исправление, предложенное @eumiro (уберите опечатанные скобки)

Редактировать: В настоящее время я иду с предложением Себастьяна использовать array с fromfile() методом, и скоро поместит здесь окончательный код. Кроме того, каждый случай был очень полезен для меня, и я очень рад поблагодарить всех, кто любезно ответил.

Окончательная форма после ввода 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 ]

15 голосов
/ 27 апреля 2011

Вы можете использовать array для считывания ваших данных:

import array
import os

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

Это в 40 раз быстрее, чем struct.unpack из @ ответа samplebias .

7 голосов
/ 27 апреля 2011

Если файлы имеют размер только 20-30M, почему бы не прочитать весь файл, декодировать числа за один вызов 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

Быстрый тест показал, что это привело к ускорению в 10 раз по сравнению с struct.unpack('h', f.read(2)) для файла 20M.

1 голос
/ 27 апреля 2011

extends () принимает итерируемые слова, то есть вместо .extend([...]) можно написать .extend(...).Вероятно, это ускорит программу, потому что extend () будет обрабатываться на генераторе, а не на встроенном списке

В вашем коде есть несогласованность: вы определяете сначала channel_content = {}и после этого вы выполняете channel_content[channel]['recording'].extend(...), для которого требуется предварительное существование ключа channel и подключа 'recording' со списком в качестве значения, чтобы иметь возможность распространяться на что-то

Каков характер self.channel_content[channel]['nsamples'], чтобы его можно было передать в функцию int () ?

Откуда number_of_intervals откуда?Какова природа интервалов?

В цикле rec in xrange(number_of_intervals)): я больше не вижу rec .Поэтому мне кажется, что вы повторяете один и тот же процесс цикла for channel in channel_names: столько раз, сколько число выражено в number_of_intervals .Есть ли значения number_of_intervals * int (self.channel_content [channel] ['nsamples']) * 2 для чтения в f?

, которые я прочитал в структуре класса doc:

.Struct (формат)

Возвращает новый объект Struct, который записывает и считывает двоичные данные в соответствии с форматом строки формата.Создание объекта Struct один раз и вызов его методов более эффективен, чем вызов функций struct с тем же форматом, поскольку строку формата нужно компилировать только один раз.

Это выражает ту же идею, что и выборки.

Если ваша цель - создать словарь, есть также возможность использовать dict () с генератором в качестве аргумента

.

EDIT

Я предлагаю

channel_content = {}
for rec in xrange(number_of_intervals)):
    for channel in channel_names:
        N = int(self.channel_content[channel]['nsamples'])
        upk = str(N)+"h", f.read(2*N)
        channel_content[channel]['recording'].extend(struct.unpack(x) for i,x in enumerate(upk) if not i%2)

Я не знаю, как принять во внимание предложение Дж. Ф. Себастьяна использовать массив

1 голос
/ 27 апреля 2011

Не уверен, что это будет быстрее, но я бы попытался декодировать куски слов вместо одного слова за раз. Например, вы можете прочитать 100 байтов данных за раз, например:

s = f.read(100)
struct.unpack(str(len(s)/2)+"h", s)
0 голосов
/ 18 апреля 2016

Определенный вызов массива из файла является самым быстрым, но он не будет работать, если наборы данных чередуются с другими типами значений.

В таких случаях еще одно значительное увеличение скорости, которое может быть объединено с предыдущими ответами struct, заключается в том, что вместо многократного вызова функции unpack предварительно скомпилируйте объект struct.Struct с форматом для каждого фрагмента.Из документов :

Создание объекта Struct один раз и вызов его методов более эффективен, чем вызов функций struct с тем же форматом, поскольку строку формата необходимо компилировать только один раз.

Так, например, если вы хотите распаковывать 1000 чередующихся шорт и поплавков за раз, вы можете написать:

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

(Обратите внимание, что пример является только частичным инеобходимо учитывать изменение размера фрагмента в конце файла и прерывание цикла while.)

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