1

最近、10MB 以下のファイルで使用するように設計された Perl で作成したクイック スクリプトが変更され、再タスク化され、40MB 以上のテキスト ファイルで使用されており、バッチ環境で重大なパフォーマンスの問題が発生していることに最近気付きました。

大きなテキスト ファイルに遭遇したとき、ジョブは 1 回の実行で約 12 時間実行されており、コードのパフォーマンスを改善するにはどうすればよいでしょうか? ファイルをメモリに丸呑みする必要があります。そうすると、ファイル内の行番号に対するジョブの依存が壊れます。建設的な考えは大歓迎です。ジョブがファイルを何度もループしていることは知っていますが、それを減らす方法はありますか?

#!/usr/bin/perl
use strict;
use warnings;

my $filename = "$ARGV[0]"; # This is needed for regular batch use 
my $cancfile = "$ARGV[1]"; # This is needed for regular batch use 
my @num =();
open(FILE, "<", "$filename") || error("Cannot open file ($!)");
while (<FILE>)
{
    push (@num, $.) if (/^P\|/)
}
close FILE;

my $start;
my $end;

my $loop = scalar(@num);
my $counter =1;
my $test;

open (OUTCANC, ">>$cancfile") || error ("Could not open file: ($!)");

#Lets print out the letters minus the CANCEL letters
for ( 1 .. $loop )
{
    $start = shift(@num) if ( ! $start );
    $end = shift(@num);
    my $next = $end;
    $end--;
    my $exclude = "FALSE";

    open(FILE, "<", "$filename") || error("Cannot open file ($!)");
    while (<FILE>)
    {
        my $line = $_;
        $test = $. if ( eof );
        if ( $. == $start && $line =~ /^P\|[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\|1I\|IR\|/)
        {
            print OUTCANC "$line";
            $exclude = "TRUECANC";
            next;
        }
        if ( $. >= $start && $. <= $end && $exclude =~ "TRUECANC")
        {
            print OUTCANC "$line";
        } elsif ( $. >= $start && $. <= $end && $exclude =~ "FALSE"){
            print $_;
        }
    }
    close FILE;
    $end = ++$test if ( $end < $start );
    $start = $next if ($next);
}


#Lets print the last letter in the file

my $exclude = "FALSE";

open(FILE, "<", "$filename") || error("Cannot open file ($!)");
while (<FILE>)
{
    my $line = $_;
    if ( $. == $start && $line =~ /^P\|[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]\|1I\|IR\|/)
    {
        $exclude = "TRUECANC";
        next;
    }
    if ( $. >= $start && $. <= $end && $exclude =~ "TRUECANC")
    {
        print OUTCANC "$line";
    } elsif ( $. >= $start && $. <= $end && $exclude =~ "FALSE"){
        print $_;
    }
}
close FILE;
close OUTCANC;


#----------------------------------------------------------------

sub message
{
    my $m = shift or return;
    print("$m\n");
}

sub error
{
    my $e = shift || 'unknown error';
    print("$0: $e\n");
    exit 0;
}
4

1 に答える 1

2

不必要な正規表現の使用を削除するなど、スクリプトを高速化できることいくつかあります。

  • /^P\|/と同等"P|" eq substr $_, 0, 2です。
  • $foo =~ "BAR"可能性があります-1 != index $foo, "BAR"

次に、いくつかの繰り返しコードがあります。それをサブルーチンに含めても、パフォーマンス自体は向上しませんが、スクリプトの動作についての推論が容易になります。

"$filename"$filenameだけで十分です。

しかし、最悪の犯罪者はこれです:

for ( 1 .. $loop ) {
  ...
  open FILE, "<", $filename or ...
  while (<FILE>) {
    ...
  }
  ...
}

そのファイルを一度だけ、できれば配列に読み込む必要があります。インデックスをループできます。

for ( 1 .. $loop ) {
  ...
  for my $i (0 .. $#file_contents) {
    my $line = $file_contents[$i];
    ... # swap $. for $i, but avoid off-by-one error
  }
  ...
}

ディスク IO は遅いため、可能な場所にキャッシュしてください。

また、変数を値と$excludeのブール値として使用していることもわかります。andではないので、条件で直接使用できますか?FALSETRUECANC01

if/elsif で一般的なテストを除外できます。

if    (FOO && BAR) { THING_A }
elsif (FOO && BAZ) { THING_B }

する必要があります

if (FOO) {
    if    (BAR) { THING_A }
    elsif (BAZ) { THING_B }
}

で始まる行数しか含まれていないため、$. == $start && $line =~ /^P\|.../テストはばかげている可能性があります。したがって、ここでは正規表現で十分です。$startP|

編集

スクリプトを正しく理解していれば、次のようにするとパフォーマンスが大幅に向上するはずです。

#!/usr/bin/perl
use strict;
use warnings;

my ($filename, $cancfile) = @ARGV;
open my $fh, "<", $filename or die "$0: Couldn't open $filename: $!";

my (@num, @lines);
while (<$fh>)
{
    push @lines, $_;
    push @num, $#lines if "P|" eq substr $_, 0, 2;
}

open my $outcanc, ">>", $cancfile or die "$0: Couldn't open $cancfile: $!";

for my $i ( 0 .. $#num )
{
    my $start = $num[$i];
    my $end   = ($num[$i+1] // @lines) - 1;
    # pre v5.10:
    # my $end = (defined $num[$i+1] ? $num[$i+1] : @lines) - 1

    if ($lines[$start] =~ /^P[|][0-9]{9}[|]1I[|]IR[|]/) {
        print {$outcanc} @lines[$start .. $end];
    } else {
        print STDOUT     @lines[$start .. $end];
    }
}

スクリプトがクリーンアップされます。ファイルは配列にキャッシュされます。配列の実際に必要な部分のみが反復されます。以前のO(n · m)からO(n)に減少しています。

将来のスクリプトについて: ループや変数の変更に関する動作を証明することは不可能ではありませんが、退屈で煩わしいものです。それを実現する

for (1 .. @num) {
  $start = shift @num unless $next;  # aka "do this only in the first iteration"
  $next = shift @num:
  $end = $next - 1:
  while (<FH>) {
    ...
    $test = $. if eof
    ...
  }
  $end = ++test if $end < $start;
  $start = $next if $next;
}

undef実際には、2番目の可能性を回避するにはshift時間がかかります。内側のループでfor をテストする代わりにeof、ループの後で行番号を選択するだけでよいので、 は必要ありません$test。次に、次のようになります。

$start = shift @num;
for my $i (1 .. @num) {
  $end = $num[$i] - 1:

  while (<FH>) { ... }

  $end = $. + 1 if $end < $start;  # $end < $start only true if not defined $num[$i]
  $start = $num[$i] if $num[$i];
}

1 つ下に変換$iした後、範囲外の問題を 1 つの点のみに限定します。

for my $i (0 .. $#num) {
  $start = $num[$i];
  $end = $num[$i+1] - 1; # HERE: $end = -1 if $i == $#num

  while (<FH>) { ... }
}
$end = $. + 1 if $end < $start;

ファイル読み取りを配列に置き換えた後 (注意してください。配列インデックスと行番号の間には 1 の違いがあります)、その反復をforループに取り込めば、最終的なファイル読み取りループを回避できることがわかります。全部で何行ありますか。つまり、私たちはそうします

$end = ($num[$i+1] // $last_line_number) - 1;

クリーンアップされたコードが元のコードと同等であることを願っています。

于 2013-08-29T11:54:18.310 に答える