2017-11-28 15 views
1

나는 주어진 패턴을 가지며 특수 문자가 포함 된 @pat_array 하나가 있습니다. Error 78?, (Not available) 77%큰 파일에서 여러 패턴이 일치하고 perl에서 일치하는 각 패턴의 수를 추적합니다.

특정 줄 번호부터 끝까지 1GB + 로그 파일의 패턴을 일치시켜야합니다. 나는 또한 각 패턴이 몇 번이나 발견되었는지 계산할 필요가있다. 아래의 코드는 작동하지만 너무 오래 걸립니다. 3 패턴의 경우 2 분에 가깝습니다.

나는 패턴을 일치시키는 동안 여분의 for 루프를 피하는 방법을 생각하고 그것을 한 번에 해냈다. (여기서 3 패턴에 대해 $ _을 pat_array의 세 가지 패턴과 비교합니다.

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]에 더 잘 맞을 것입니다. 이미 작업 코드가 있으며이를 개선하려고합니다. 어쨌든, [edit] 테스트 할 두 줄의 로그 라인을 포함시키고 패턴 목록을 보여주십시오. 해당 정보를 공개하고 싶지 않은 경우 동일한 문제를 보여주는 예제 데이터가 포함 된 새로운 전체 프로그램을 작성하고 포함하십시오. 이를 [mcve]라고합니다. – simbabque

+0

_ 새로운 코드/아이디어로 나를 도울 때 ._ "언제"는 – ssr1012

+2

xpost가/대답으로 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'은 패턴이 단어 char로 시작하고 끝나는 것으로 가정하기 때문에 작동하지 않지만 적어도 하나의 패턴 ('(사용할 수 없음) 77 %')은 비 단어로 시작하고 끝납니다. – ikegami

+0

좋은 지적입니다. 나는 편집 할 것이다. – Sobrique

2

이 답변을 통해 스크립트를 왜 프로파일로 작성하여 느린 이유와 장소를 알 수 있습니다. CPAN에서 설치해야하는 Perl 프로파일 러 Devel::NYTProf을 사용합니다.

직접 해보기 전에 저자에 대해 프로파일 링에 대해 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. 

다음으로 스크립트를 준비합니다. 필자는보다 현대적인 Perl을 만들기 위해 몇 가지 변경을했습니다.이 세 인수 인 open과 어휘 파일 핸들과 같습니다.

$ 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입니다.이 라인은 폐기 된 라인에서도 호출됩니다. 물론이 예제에서는 한 줄만 건너 뛰었지만, 그럴 수도 있습니다. if 뒤에 chomp을 입력하십시오.

NYTProf original patterns.pl

우리는 또한 가장 중요한 시간이 if에 소요되는 것을 볼 수 있습니다. chorboa says in his answer on your cross-posted Perlmonks question으로 명명 된 캡처 그룹과 함께 단일 패턴을 사용할 수 있습니다. 저는 이것을 두 단계로 시연 해 보았습니다. 그래서 그가 왜 그가 한 일을하는지 봅니다.

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

그것은 거의 1 백만 라인의 하나 하나에 대해 새롭게 동일한 패턴을 컴파일, 라인 (18)에 그 루프 내에서 거의 2 초 보냈다.

그래서 루프에서 벗어나기를 바랄 것입니다. 예를 들어, choroba가 자신의 게시물에있는 것처럼 말입니다.

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 개 대신 루프에서 하나의 일치 항목을 얻는 이점은 매우 명확하지만 어떤 패턴이 일치했는지 파악하는 캡처 그룹은 가격이 높으며 매번 생성되는 조회 해시가있는 명명 된 캡처 그룹은 훨씬 비쌉니다 .

대신이 값을 솔루션 in Sobrique's answer과 비교하면 3.78s이고 원래 1.78s + 23.6s입니다. 이 차이는 이제 거의 한 자리수로 매우 중요합니다. 순서 번호를 사용하여 패턴을 얻으려면 루프 외부에 하나 또는 두 줄의 추가 코드를 작성해야합니다. 이는 무시할 수 있습니다.


모든 측정 값은 기계마다 크게 다르며 동시에 실행되는 다른 프로세스의 영향을받습니다. 귀하의 컴퓨터에서 그들은 완전히 다를 수 있습니다. 벤치마킹은 어렵고 종종 정확하지 않습니다.