Perl быстрее, чем bash? - PullRequest
       49

Perl быстрее, чем bash?

11 голосов
/ 15 июля 2009

У меня есть сценарий bash, который вырезает часть файла журнала между 2 временными метками, но из-за размера файлов для запуска требуется довольно много времени.

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

#!/bin/bash

if [ $# -ne 3 ]; then
  echo "USAGE $0 <logfile(s)> <from date (epoch)> <to date (epoch)>"
  exit 1
fi

LOGFILES=$1
FROM=$2
TO=$3
rm -f /tmp/getlogs??????
TEMP=`mktemp /tmp/getlogsXXXXXX`

## LOGS NEED TO BE LISTED CHRONOLOGICALLY
ls -lnt $LOGFILES|awk '{print $8}' > $TEMP
LOGFILES=`tac $TEMP`
cp /dev/null $TEMP

findEntry() {
  RETURN=0
  dt=$1
  fil=$2
  ln1=$3
  ln2=$4
  t1=`tail -n+$ln1 $fil|head -n1|cut -c1-15`
  dt1=`date -d "$t1" +%s`
  t2=`tail -n+$ln2 $fil|head -n1|cut -c1-15`
  dt2=`date -d "$t2" +%s`
  if [ $dt -ge $dt2 ]; then
    mid=$dt2
  else
    mid=$(( (($ln2-$ln1)*($dt-$dt1)/($dt2-$dt1))+$ln1 ))
  fi
  t3=`tail -n+$mid $fil|head -n1|cut -c1-15`
  dt3=`date -d "$t3" +%s`
  # finished
  if [ $dt -eq $dt3 ]; then
    # FOUND IT (scroll back to the first match)
    while [ $dt -eq $dt3 ]; do
      mid=$(( $mid-1 ))
      t3=`tail -n+$mid $fil|head -n1|cut -c1-15`
      dt3=`date -d "$t3" +%s`
    done
    RETURN=$(( $mid+1 ))
    return
  fi
  if [ $(( $mid-1 )) -eq $ln1 ] || [ $(( $ln2-1)) -eq $mid ]; then
    # FOUND NEAR IT
    RETURN=$mid
    return
  fi
  # not finished yet
  if [ $dt -lt $dt3 ]; then
    # too high
    findEntry $dt $fil $ln1 $mid
  else
    if [ $dt -ge $dt3 ]; then
      # too low
      findEntry $dt $fil $mid $ln2
    fi
  fi
}

# Check timestamps on logfiles
LOGS=""
for LOG in $LOGFILES; do
  filetime=`ls -ln $LOG|awk '{print $6,$7}'`
  timestamp=`date -d "$filetime" +%s`
  if [ $timestamp -ge $FROM ]; then
    LOGS="$LOGS $LOG"
  fi
done

# Check first and last dates in LOGS to refine further
for LOG in $LOGS; do
    if [ ${LOG%.gz} != $LOG ]; then
      gunzip -c $LOG > $TEMP
    else
      cp $LOG $TEMP
    fi
    t=`head -n1 $TEMP|cut -c1-15`
    FIRST=`date -d "$t" +%s`
    t=`tail -n1 $TEMP|cut -c1-15`
    LAST=`date -d "$t" +%s`
    if [ $TO -lt $FIRST ] || [ $FROM -gt $LAST ]; then
      # This file is entirely out of range
      cp /dev/null $TEMP
    else
      if [ $FROM -le $FIRST ]; then
        if [ $TO -ge $LAST ]; then
          # Entire file is within range
          cat $TEMP
        else
          # Last part of file is out of range
          STARTLINENUMBER=1
          ENDLINENUMBER=`wc -l<$TEMP`
          findEntry $TO $TEMP $STARTLINENUMBER $ENDLINENUMBER
          head -n$RETURN $TEMP
        fi
      else
        if [ $TO -ge $LAST ]; then
          # First part of file is out of range
          STARTLINENUMBER=1
          ENDLINENUMBER=`wc -l<$TEMP`
          findEntry $FROM $TEMP $STARTLINENUMBER $ENDLINENUMBER
          tail -n+$RETURN $TEMP
        else
          # range is entirely within this logfile
          STARTLINENUMBER=1
          ENDLINENUMBER=`wc -l<$TEMP`
          findEntry $FROM $TEMP $STARTLINENUMBER $ENDLINENUMBER
          n1=$RETURN
          findEntry $TO $TEMP $STARTLINENUMBER $ENDLINENUMBER
          n2=$RETURN
          tail -n+$n1 $TEMP|head -n$(( $n2-$n1 ))
        fi
      fi
    fi
done
rm -f /tmp/getlogs??????

Ответы [ 10 ]

27 голосов
/ 15 июля 2009

Perl нелепо быстрее, чем Bash. А для манипулирования текстом на самом деле вы можете добиться лучших результатов с Perl, чем с C, если вы не тратите время на написание сложных алгоритмов. Конечно, для простых вещей C может быть непобедимым.

Тем не менее, если ваш сценарий "bash" не зацикливается, а просто вызывает другие программы, тогда никакой выгоды не будет. Например, если ваш скрипт выглядит как "cat X | grep Y | tr -f 3-5 | sort | uniq", то большую часть времени тратится на cat, grep, tr, sort и uniq, НЕ на Bash.

Вы получите производительность, если в скрипте есть какой-либо цикл, или если вы сохраните несколько чтений одного и того же файла.

Вы говорите, что вырезали вещи между двумя отметками времени в файле. Допустим, ваш скрипт на Bash выглядит так:

LINE1=`grep -n TIMESTAMP1 filename | head -1 | cut -d ':' -f 1`
LINE2=`grep -n TIMESTAMP2 filename | head -1 | cut -d ':' -f 1`
tail +$LINE1 filename | head -$(($LINE2-$LINE1))

Тогда вы увеличите производительность, потому что вы читаете весь файл три раза: один раз для каждой команды, где появляется «имя файла». В Perl вы бы сделали что-то вроде этого:

my $state = 0;
while(<>) {
  exit if /TIMESTAMP2/;
  print $_ if $state == 1;
  $state = 1 if /TIMESTAMP1/;
}

Это прочитает файл только один раз, а также остановится, как только вы прочитаете TIMESTAMP2. Поскольку вы обрабатываете несколько файлов, вы должны использовать «last» или «break» вместо «exit», чтобы скрипт мог продолжать обрабатывать файлы.

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

  1. Прочитайте ВЕСЬ файл для подсчета строк!
  2. Делать несколько хвостов в файле
  3. Завершить "head" или "tail" файл еще раз

Кроме того, возглавьте свои хвосты. Каждый раз, когда вы делаете это, какой-то фрагмент кода читает эти данные. Некоторые из этих строк читаются до 10 или более раз!

18 голосов
/ 15 июля 2009

Вы почти наверняка получите огромное преимущество в скорости написания своего скрипта на Perl, просто обрезав файл, прочитанный при передаче второй метки времени.

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

12 голосов
/ 15 июля 2009

Обновлен скрипт на основе комментария Брента: Этот тест не проверен.

#!/usr/bin/perl

use strict;
use warnings;

my %months = (
    jan => 1, feb => 2,  mar => 3,  apr => 4,
    may => 5, jun => 6,  jul => 7,  aug => 8,
    sep => 9, oct => 10, nov => 11, dec => 12,
);

while ( my $line = <> ) {
    my $ts = substr $line, 0, 15;
    next if parse_date($ts) lt '0201100543';
    last if parse_date($ts) gt '0715123456';
    print $line;
}

sub parse_date {
    my ($month, $day, $time) = split ' ', $_[0];
    my ($hour, $min, $sec) = split /:/, $time;
    return sprintf(
        '%2.2d%2.2d%2.2d%2.2d%2.2d',
        $months{lc $month}, $day,
        $hour, $min, $sec,
    );
}


__END__

Предыдущий ответ для справки: Какой формат файла? Вот короткий скрипт, который предполагает, что первый столбец является временной меткой и печатает только строки, которые имеют временные метки в определенном диапазоне. Также предполагается, что метки времени отсортированы. В моей системе отфильтровывание 900 000 строк из миллиона заняло около секунды:

#!/usr/bin/perl

use strict;
use warnings;

while ( <> ) {
    my ($ts) = split;
    next if $ts < 1247672719;
    last if $ts > 1252172093;
    print $ts, "\n";
}

__END__
5 голосов
/ 15 июля 2009

Исходя из имеющегося у вас шелл-кода с несколькими вызовами к tail / head, я бы сказал, абсолютно Perl может быть быстрее. C может быть даже быстрее, но время разработки, вероятно, не будет стоить этого, поэтому я остановлюсь на Perl. (Я говорю «мог», потому что вы можете писать сценарии оболочки на Perl, и я видел их достаточно, чтобы съежиться. Это, очевидно, не дало бы желаемого преимущества в скорости).

Perl имеет более высокую стоимость запуска, или так утверждается. Честно говоря, я никогда не замечал. Если ваша альтернатива заключается в том, чтобы сделать это на Java, Perl не имеет затрат на запуск. По сравнению с Башом я просто не заметил. Что я заметил, так это то, что по мере того, как я отказываюсь от вызова всех специализированных инструментов Unix, которые хороши, когда у вас нет альтернатив, и стремлюсь делать все это в одном процессе, скорость растет. Затраты на создание новых процессов в Unix не так серьезны, как это могло бы быть в Windows, но все же они не настолько незначительны, так как вам приходится каждый раз переинициализировать библиотеку времени выполнения C (libC), анализировать аргументы, открывать файлы (возможно) и т. д. В Perl вы в конечном итоге используете огромные участки памяти при передаче всего списка или чего-то в этом роде, но все это в памяти, так что это быстрее. И многие из инструментов, к которым вы привыкли, либо встроены (map/grep, регулярные выражения), либо доступны в модулях на CPAN. Хорошая комбинация из них сделает работу легко.

Главное - не перечитывать файлы. Это дорого. И вы делаете это много раз. Черт возьми, вы можете использовать модификатор :gzip в open для непосредственного чтения ваших файлов gzip, сохраняя еще один проход - и это будет быстрее, так как вы будете меньше читать с диска.

2 голосов
/ 15 июля 2009

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

2 голосов
/ 15 июля 2009

Я бы профилировал все три решения и выбрал, который является лучшим с точки зрения начальной скорости запуска, скорости обработки и использования памяти.

Что-то вроде Perl / Python / Ruby может быть не самым быстрым, но вы можете быстро развиваться на этих языках - намного быстрее, чем в C и даже в Bash.

1 голос
/ 15 июля 2009

В вашем скрипте bash, поместите это:

perl -ne "print if /$FROM/../$TO/" $LOGFILES

$ FROM и $ TO - это регулярное выражение для вашего времени начала и окончания.

Они включены, поэтому вы можете поставить 2009-06-14 23:59:59 на ваше время окончания, поскольку 2009-06-15 00:00:00 будет включать транзакции в полночь.

1 голос
/ 15 июля 2009

Я согласен, что переход от сценария только для bash к Perl (или даже к awk, если среда perl недоступна) может дать выигрыш в скорости, если предположить, что оба написаны одинаково хорошо.

Однако, если извлечение поддается формированию сценария bash, который создает параметры и затем вызывает grep с регулярным выражением, это может быть быстрее, чем «чистый» сценарий.

1 голос
/ 15 июля 2009

bash на самом деле читает файл по очереди за раз, поскольку он интерпретирует его на лету (о чем вам будет больно знать, если вы когда-нибудь измените сценарий bash, пока он все еще выполняется), вместо предварительной загрузки и разбирать все сразу. Так что да, Perl, как правило, будет намного быстрее, если вы будете делать то, что обычно не делаете в bash в любом случае.

0 голосов
/ 16 июля 2009

Хорошо, bash интерпретируется построчно, поскольку он работает и зависит от вызова большого количества внешних программ (в зависимости от того, что вы хотите сделать). Вам часто приходится использовать временные файлы в качестве промежуточного хранилища для наборов результатов. Он (оболочка) изначально был предназначен для взаимодействия с системой и автоматизации последовательностей cmd (файлов оболочки).

Perl больше похож на C, он в значительной степени самодостаточен с огромной библиотекой бесплатного кода и скомпилирован, поэтому он работает намного быстрее, например, примерно на 80-90% скорости C, но проще в программировании (например, переменные размеры динамические ).

...