Как я могу ускорить обработку Perl данных фиксированной ширины?

У нас есть готовый код, который загружает данные из файлов в базу данных. Существует несколько форматов файлов; все они являются полями фиксированной ширины.

Часть кода использует функцию Perl unpack() для чтения полей из входных данных в переменные пакета. Затем бизнес-логика может ссылаться на эти поля в «удобочитаемом» виде.

Код чтения файла генерируется из описания формата один раз перед чтением файла.

В виде скетча сгенерированный код выглядит так:

while ( <> ) {

    # Start of generated code.

    # Here we unpack 2 fields, real code does around 200.
    ( $FIELDS::transaction_date, $FIELDS::customer_id ) = unpack q{A8 A20};

    # Some fields have leading space removed
    # Generated code has one line like this per affected field.
    $FIELDS::customer_id =~ s/^\s+//;

    # End of generated code.

    # Then we apply business logic to the data ...
    if ( $FIELDS::transaction_date eq $today ) {
        push @fields, q{something or other};
    }

    # Write to standard format for bulk load to the database.
    print $fh join( '|', @fields ) . q{\n} or die;
}

Профилирование кода показывает, что около 35% времени тратится на распаковку и полосу начального пробела. Оставшееся время уходит на проверку и преобразование данных, а также на запись в выходной файл.

Похоже, что ни одна часть бизнес-логики не занимает более 1-2% времени выполнения.

Вопрос в том, можем ли мы каким-то образом увеличить скорость распаковки и удаления пробелов? Желательно без рефакторинга всего кода, который ссылается на переменные пакета FIELDS.

ИЗМЕНИТЬ:

В случае, если это имеет значение

$ perl -v
This is perl, v5.8.0 built for PA-RISC1.1

person martin clayton    schedule 19.10.2009    source источник
comment
Было бы интересно узнать, будет ли использование списка переменных пакета в левой части распаковки оптимальным.   -  person martin clayton    schedule 20.10.2009


Ответы (6)


да. Извлечение с помощью substr, вероятно, будет самым быстрым способом сделать это. Это:

$FIELDS::transaction_date = substr $_, 0, 8;
$FIELDS::customer_id      = substr $_, 8, 20;

скорее всего будет быстрее. Теперь, если бы я писал этот код от руки, я бы не отказался от unpack, но если вы генерируете код, вы можете попробовать и измерить.

См. также ответы на вопрос Является ли unpack() в Perl быстрее, чем substr()?< /а>

Что касается удаления начальных пробелов, s/^\s+//, вероятно, будет самым быстрым методом.

Обновление: трудно сказать что-то определенное, не имея возможности провести тесты. Однако как насчет:

my  $x  = substr $_, 0, 8;

для полей, не требующих обрезки и

my ($y) = substr($_, 8, 20) =~ /\A\s+(.+?)\s+\z/;

которые нуждаются в обрезке?

person Sinan Ünür    schedule 19.10.2009
comment
Буду экспериментировать и отчитаюсь. - person martin clayton; 19.10.2009
comment
Следует отметить, что распаковка «A» удаляет конечные пробелы «бесплатно». - person martin clayton; 19.10.2009
comment
Пока выглядит многообещающе. Базовый бенчмаркинг показывает, что серия подстрок для каждого поля и завершающих регулярных выражений полосы для нас примерно на 50% быстрее, чем использование одной распаковки. Тесты проходят, но мой экран забит Perlish-предупреждениями, так что я еще не совсем там. - person martin clayton; 19.10.2009
comment
@martin Clayton: Какие предупреждения? - person Sinan Ünür; 19.10.2009
comment
Ничего судебного дефолта || не мог позаботиться. У нас есть некоторые данные, которые не заполняют всю запись. Я поэкспериментирую с разными пробелами в конце, потому что без них версия substr работает намного быстрее, чем unpack. - person martin clayton; 19.10.2009
comment
.+ является жадным, поэтому вы оставите один пробел в конце строки. Вы хотите .+? там. - person runrig; 22.10.2009

Я действительно имел дело с этой проблемой снова и снова. Unpack лучше, чем substr.

Что касается зачистки пробелов, вы в значительной степени облажались. Этот взлом регулярных выражений является «официальным» способом сделать это. Возможно, вы сможете повысить эффективность, уточнив операторы распаковки (при условии, что данные не длиннее 4 цифр, зачем распаковывать полные 12 цифр поля?), но в остальном синтаксический анализ — это просто p.i.t.a.

Удачи с вашими плоскими данными. Чертов устаревший хлам, как я его ненавижу.

person Satanicpuppy    schedule 19.10.2009

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

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

person ttarchala    schedule 19.10.2009
comment
Почти уверен, да. В реальном коде ввод-вывод и распаковка разделены, в отличие от приведенного выше примера. Параллелизм — хороший выбор — мы уже это делаем. Тем не менее, хотелось бы тратить меньше времени на обрезку струн. - person martin clayton; 19.10.2009
comment
Для меня самым большим узким местом является сама база данных; если вы имеете дело с плоскими данными, вы вполне можете иметь дело с ужасной древней плоской базой данных. Я обхожу эту проблему, периодически загружая данные в современную базу данных, но для данных в реальном времени почти всегда причиной замедления является база данных. - person Satanicpuppy; 19.10.2009
comment
Приходится соглашаться. В целом наш процесс тратит большую часть времени на ввод данных в базу данных. Мы можем использовать параллельные процессы, чтобы ускорить это. Меня поразило, что, поскольку такая большая часть «Perl-времени» была потрачена на такую ​​маленькую часть кода, здесь может быть место для легкой оптимизации. - person martin clayton; 20.10.2009

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

person weismat    schedule 19.10.2009
comment
Спасибо. Вероятно, для моего проекта выигрыш не оправдал бы дополнительной сложности. Как заметил Satanicpuppy, есть и другие части процесса, которые обходятся дороже. Хотя может работать для кого-то еще с похожей проблемой. - person martin clayton; 20.10.2009
comment
Если большая часть времени ЦП тратится на движок регулярных выражений и встроенную функцию, то большая часть времени в любом случае тратится на C (т.е. не на ходьбу opttrees и обработку структур данных perl). И нужен хороший программист, чтобы победить людей, которые написали Perl VM. распаковать быстро! - person tsee; 20.10.2009

Просто сделайте так, чтобы они шли параллельно. Это тривиально, и на любой даже отдаленно современной машине это будет быстрее.

person Community    schedule 20.10.2009

Тест версии нашего кода на основе substr показал, что она может быть примерно на 50% быстрее, чем наша существующая распаковка. Сравнивая коды в реальном приложении, версия substr дала нам сокращение времени выполнения на 16%. Это близко к тому, на что мы надеялись, основываясь на эталонном тесте и профилировании, упомянутом в вопросе.

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

Теперь у нас есть идиома:

$FIELDS::transaction_date = substr( $_, 0, 8 ) || '';
$FIELDS::transaction_date =~ s/\s+\z//;
$FIELDS::customer_id = substr( $_, 8, 20 ) || '';
$FIELDS::customer_id =~ s/\s+\z//;

Затем следует выборочное удаление ведущего пробела, как и раньше.

Спасибо за все ответы. Я соглашусь с Синаном, потому что это сработало для нас, несмотря на то, что кажется «просто неправильным».

person Community    schedule 20.10.2009