2017-11-28 1 views
1

私には与えられたパターンを持ち、特殊文字を含む@pat_arrayが1つあります。 Error 78?(Not available) 77%など複数のパターンが大きなファイルでマッチし、perlでマッチした各パターンの数を追跡します

1GB +ログファイル内のパターンを特定の行番号から最後まで一致させる必要があります。私はまた、各パターンが何回検出されたのかを数えておく必要があります。下のコードは機能しますが、時間がかかりすぎます:3パターンで2分近く。

私は、パターンを一致させてワンショットで行うと、余分なforループを避ける方法を考えていました。 (ここでは3パターン、$ _はpat_arrayの3つのパターンと一致しています)。

my @pat_array = split('@@@', $InListOfPatterns); 
my $num_pat = @pat_array; 

my @match_count; 
for (my $i = 0; $i < $num_pat; $i = $i + 1) { 
    $match_count[$i] = 0; 
} 

open LOG_READ, '<', "$InLogFilePath" || die "can not open file :$!"; 

while (<LOG_READ>) { 
    chomp; 

    if ($. > $InStartLineNumber) { 

     for (my $j = 0; $j < $num_pat; $j = $j + 1) { 

      if ($_ =~ m/\Q$pat_array[$j]\E/) { 
       $match_count[$j] = ($match_count[$j] + 1); 
      } 
     } 
    } 
} 

close(LOG_READ); 
+4

あなたの質問は[codereview.se]でうまく収まるでしょう。あなたはすでに作業コードを持っていて、改善したいと思っています。いずれにせよ、[編集]し、テストするログの完全な行をいくつか含めて、パターンのリストを表示してください。その情報を開示したくない場合は、同じ問題を示すサンプルデータを含む新しいプログラムを作成し、それを含めます。これは[mcve]と呼ばれます。 – simbabque

+0

_新しいコード/アイデアを手伝ってくれたとき._「いつ」は – ssr1012

+2

xpost w/answers http://www.perlmonks.org/?node_id=1204414 – toolic

答えて

2

このように長い時間がかかる理由は、ループネストが原因です。あなたはパターンのリストを読んでいて、それぞれの行についてそれぞれを試しています。

非常に非効率です。

use Data::Dumper; 

my @pat_array = split('@@@', $InListOfPatterns); 

my $match_regex = join '|', map { quotemeta } @pat_array; 
    $match_regex = qr/($match_regex)/; 

print "Using match regex of: ", $match_regex,"\n"; 

my %count_of;  
open my $log_read, '<', $InLogFilePath or die "can not open file :$!"; 
while (<$log_read>) { 
    next unless $. > $InStartLineNumber; 
    chomp; 
    m/$match_regex/ && $count_of{$1}++; 
} 
close($log_read); 

print Dumper \%count_of; 

それは、単一のキャプチャ正規表現を遵守し、%count_ofハッシュで試合を捕捉し、行ごとに一度、それを実行します。このような

何かが同じことを行います。しかし、のキャプチャ結果では、正規表現ではありません(あなたの例とは異なります)。 quotemeta(リテラルパターンを与えるには\Q\Eと同様に動作します)の場合、これは重要ではありません。

また、open行にエラーがあります。||の優先度が高すぎるため、動作しません。あなたのコードの機能として:dieは場合に発生することを意味し

open LOG_READ, '<', ("$InLogFilePath" || die "can not open file :$!"); 

- とする場合にのみ - $InLogFilePath偽(未定義または空)です。

代わりにorを使用してください。この問題はありません。または角括弧を追加します。

+0

これは楽しいあなたのソリューションは、Perl Monksのchorobaよりも約8倍高速です。しかし、パターンのインデックスを使用することは許可されていません。 – simbabque

+1

これらの '\ b'は、パターンが単語の文字で始まり、終わると仮定しているので動作しませんが、少なくとも1つのパターン('(利用不可能)77% ')は両方とも非単語で始まり、終わりです。 – ikegami

+0

それは良い点です。私は編集します。 – Sobrique

2

この回答は、スクリプトがなぜ遅いのかと理由を確認する方法を教えてくれます。 PerlプロファイラDevel::NYTProfを使用します。これはCPANからインストールする必要があります。

これを行う前に、this talkをプロファイリングについての著者に見てください。これはまれにしか行われないことを知っておくことが重要です。あなたはそのようなケースです。

まず、the JSON API of baconipsum.comを使用して、次のコマンドを使用してテスト入力を作成します。このファイルは70メガバイト程度で1万行を持つことになります

$ curl -s \ 
    'https://baconipsum.com/api/?type=meat-and-filler&format=text&sentences=100' \ 
    | perl -nE 'for $i (1 .. 10_000) { say for map lc, split /\./}' >log.txt 

。それだけで十分です。

$ ls -lah 
-rw-rw-r-- 1 simbabque simbabque 69M Nov 28 13:36 log.txt 
$ tail log.txt 
Nisi magna pig pastrami, in chicken elit meatball 
Consequat laborum rump kevin beef ham hock proident tempor ex strip steak 
Shankle kielbasa in nulla 
Consectetur picanha pork belly, drumstick tail tempor alcatra pariatur eiusmod 
Tongue tail meatloaf cupim ut do sed, cillum kevin id ex dolore t-bone 
Ut cow nulla brisket ball tip ipsum ham strip steak culpa cillum 
Doner chicken sint duis in, andouille labore eiusmod 
Bacon tempor nostrud, short loin occaecat cow nulla ipsum strip steak pastrami corned beef turducken 
Ball tip labore chicken pancetta cupim 
Ham leberkas pastrami, exercitation id porchetta tri-tip beef voluptate shoulder ipsum meatloaf sunt ea. 

次に、スクリプトを準備します。私は、3つの引数openとレキシカルファイルハンドルのように、より現代的なPerlにするためにいくつかの変更を加えました。

$ cat patterns.pl 
use strict; 
use warnings; 
use Data::Dumper; 

my $InListOfPatterns = '[email protected]@@[email protected]@@steak'; 
my $InStartLineNumber = 2; 

my @pat_array = split('@@@', $InListOfPatterns); 
my $num_pat = @pat_array; 
my @match_count; 
for (my $i = 0; $i < $num_pat; $i = $i + 1) { 
    $match_count[$i] = 0; 
} 

open my $fh,'<','log.txt' or die "can not open file :$!"; 
while (<$fh>) { 
    chomp; 
    if ($. > $InStartLineNumber) { 
     for (my $j = 0; $j < $num_pat; $j = $j + 1) { 
      if ($_ =~ m/\Q$pat_array[$j]\E/) { 
       $match_count[$j] = ($match_count[$j] + 1); 
      } 
     } 
    } 
} 

print Dumper \@match_count; 

これは私のマシンでは6秒のようなもので、最後にパターンごとの一致数が表示されます。

ここでは、Devel::NYTProfでこれをどのようにプロファイルできるかを見ていきましょう。このコマンドを実行するだけです。 -dフラグはPerlにデバッガインタフェースを使用するように指示し、:NYTProfDevel::NYTProfデバッガを使用するように指示します。

$ perl -d:NYTProf patterns.pl 
$VAR1 = [ 
      20000, 
      300000, 
      90000 
     ]; 

あなたのディレクトリにnytprof.outというファイルがあります。

$ nytprofhtml --no-flame --open 
Reading nytprof.out 
Processing nytprof.out data 
Writing line reports to nytprof directory 
100% ... 

それは、既存のブラウザでブラウザウィンドウまたは新しいタブを開き、あなたにこのような何かが表示されます:

NYTProf index

我々はラインレポートに行きたいですpatterns.pl。赤い線は、NYTProfが非常に遅いと判断するものです。

最も明白なのは、行17のchompです。これは、破棄された行でも呼び出されます。もちろん、私たちの例では1行をスキップしただけでしたが、あなたの場合はそれ以上であるかもしれません。 ifの後にchompを移動します。

NYTProf original patterns.pl

は、我々はまた、最も重要な時間はifに費やされていることがわかります。 chorboa says in his answer on your cross-posted Perlmonks questionという名前のキャプチャグループでは、単一のパターンを使用できます。私はこれを2つのステップで実証するので、なぜ彼は彼がしたことをしたのか分かります。

use strict; 
use warnings; 
use Data::Dumper; 

my $InListOfPatterns = '[email protected]@@[email protected]@@steak'; 
my $InStartLineNumber = 2; 

my @pat_array = split('@@@', $InListOfPatterns); 

open my $fh, '<', 'log.txt' or die "can not open file :$!"; 
my %matched; 
while (<$fh>) { 
    if ($. > $InStartLineNumber) { 
     chomp; 

     # these two are inside the loop, which is bad 
     my $i; 
     my $regex = join '|', map +($i++, "(?<m$i>$_)")[1], map quotemeta, @pat_array; 

     $matched{ (grep defined $-{$_}[0], keys %-)[0] }++ if /$regex/; 
    } 
} 

print Dumper \%matched; 

プロファイラをもう一度実行し、結果を確認してください。ループの内部ではより複雑な操作を行うため、処理に時間がかかります。それは良くないね。

NYTProf patterns.pl first round

それはほぼ100万行のすべての単一ため改めて同一のパターンをコンパイルし、ライン18上のそのループの内側に約2秒を過ごしました。

だから、あなたは明らかにループの外に移動したいと思っています。例えば、チョロバが彼の投稿に持っていたように。

use strict; 
use warnings; 
use Data::Dumper; 

my $InListOfPatterns = '[email protected]@@[email protected]@@steak'; 
my $InStartLineNumber = 2; 

my @pat_array = split('@@@', $InListOfPatterns); 
my $i; 
my $regex = join '|', map +($i++, "(?<m$i>$_)")[1], map quotemeta, @pat_array; 

open my $fh, '<', 'log.txt' or die "can not open file :$!"; 
my %matched; 
while (<$fh>) { 
    if ($. > $InStartLineNumber) { 
     chomp; 

     $matched{ (grep defined $-{$_}[0], keys %-)[0] }++ if /$regex/; 
    } 
} 

print Dumper \%matched; 

これをプロファイラで再実行すると、これが報告されます。

NYTProf patterns.pl second round

パターン生成のための呼び出しは、今一度だけ呼ばれます。それははるかに高速です。

残念ながら、このファイルサイズでは、全体的な勝利はあまり高くありません。元のコードのforループでは12.4秒+1.1秒でしたが、今は12.2です。それらの1.3秒は、わずか100万行ではあまり意味がありません。しかし、ファイルにはさらに多くの行がある可能性があります。特に可能なパターンを追加すると、合計で少し速くなります。

パターンを5つに増やすと、新しい実装では23.6秒、元の実装では1.78秒+ 23.6秒になります。それは1.78sの違いです。

3つではなくループ内で1つの一致が得られるというメリットは非常にはっきりしていますが、どのパターンが一致しているかを把握するキャプチャグループは価格が高く、 。

代わりにこれをソリューションin Sobrique's answerと比較すると、元の1.78s + 23.6sに対して3.69sとなります。この差は現在ほぼ1桁であり、非常に重要です。序数でパターンを取得するには、ループ外に1行または2行の追加コードを書く必要がありますが、これはごくわずかです。


すべての測定値はマシンによって大きく異なり、同時に実行される他のプロセスの影響を受けることに注意してください。あなたのコンピュータ上では全く異なるかもしれません。ベンチマークは難しく、しばしばあまり正確ではありません。

関連する問題