2012-04-20 16 views
23

Zauważyłem pewne ekstremalne opóźnienia w moich skryptach Ruby (1.9), a po pewnym wykopaniu sprowadziłem się do dopasowywania wyrażenia regularnego. Używam następujących skryptów testowych w Perl i Ruby:Wyrażenie regularne - Ruby vs Perl

Perl:

$fname = shift(@ARGV); 
open(FILE, "<$fname"); 
while (<FILE>) { 
    if (/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) { 
     print "$1: $2\n"; 
    } 
} 

Ruby:

f = File.open(ARGV.shift) 
while (line = f.gets) 
    if /(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/.match(line) 
     puts "#{$1}: #{$2}" 
    end 
end 

używać tego samego wejścia do obu skryptów, A plik tylko 44290 linii. Moment dla każdego z nich to:

Perl:

[email protected]:~/bin/local/project$ time ./try.pl input >/dev/null 

real 0m0.049s 
user 0m0.040s 
sys  0m0.000s 

Ruby:

[email protected]:~/bin/local/project$ time ./try.rb input >/dev/null 

real 1m5.106s 
user 1m4.910s 
sys  0m0.010s 

Chyba robię coś strasznie głupiego, jakieś sugestie?

Dziękuję

+2

Czy próbowałeś 'jeśli linia = ~ /(.*) \ |?.?.?. * Wysyłanie żądania * TID = (*) /'? To też działa w Ruby, byłbym ciekawy, czy ma inne cechy wydajności. –

Odpowiedz

7
regex = Regexp.new(/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) 

f = File.open(ARGV.shift).each do |line| 
    if regex .match(line) 
     puts "#{$1}: #{$2}" 
    end 
end 

Albo

regex = Regexp.new(/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) 

f = File.open(ARGV.shift) 
f.each_line do |line| 
    if regex.match(line) 
    puts "#{$1}: #{$2}" 
    end 
+0

+1 Stwierdziłem, że Perl robi to automatycznie. – stema

+3

Próbowałem twojej sugestii, ale nie było żadnej zmiany, czas wykonania nadal wynosi 1m5.134s – xpapad

+2

Kilka wykałaczek: musisz zwolnić deskryptor pliku, gdy skończysz, nazywając 'close' lub używając' Plik .open ("filename") {| file | } ', co zapewnia zamknięcie pliku. Również '/#{...}/' oznacza literał "Regexp"; wywołanie "Regexp.new" jest niepotrzebne. –

5

W sekcji perlretut chapter: Using regular expressions in Perl - "Znajdź i zastąp"

(Choć wyrażenie regularne pojawi się w pętli, Perl jest wystarczająco inteligentny, aby skompilować go tylko raz.)

Nie wiem, bardzo dobrze Ruby, ale podejrzewam, że kompiluje regex w każdym cyklu.
(Wypróbuj kod z odpowiedzi LaGrandMere, aby to sprawdzić).

+0

Wątpię w to. Jest na to specjalna składnia, więc prawdopodobnie została zbudowana podczas fazy parsowania ... czyli przed pętlą. – remram

5

Jedną z możliwych różnic jest ilość wykonywanych nawrotów. Perl może lepiej wykonać przycinanie drzewa wyszukiwania podczas cofania (to znaczy zauważenie, kiedy część wzorca nie może się równać). Jego silnik regex jest wysoce zoptymalizowany.

Po pierwsze, dodanie wiodącej « ^ » może zrobić ogromną różnicę. Jeśli wzór nie pasuje do pozycji 0, nie będzie się on równać w pozycji początkowej 1! Więc nie spróbować dopasować w pozycji 1.

Według tych samych zasad, « .*? » nie jako ograniczenie, jak myślisz, i zastępując każde wystąpienie ją bardziej ograniczający wzór mógłby zapobiec wielu backtracking .

Dlaczego nie spróbować:

/ 
    ^
    (.*?)      [ ]\| 
    (?:(?!SENDING[ ]REQUEST).)* SENDING[ ]REQUEST 
    (?:(?!TID=).)*    TID= 
    ([^,]*)      , 
/x 

(Nie jestem pewien, czy to jest bezpieczne, aby zastąpić pierwsze « .*? » z « [^|] », więc ja nie.)

(przynajmniej dla wzorów pasujących pojedynczy łańcuch, (?:(?!PAT).) jest PAT jak [^CHAR] jest CHAR).

Korzystanie /s mogłaby przyspieszyć jeśli « . » jest dozwolony, aby dopasować znaki nowej linii, ale myślę, że to jest niewielkie.

Korzystanie « \space » zamiast « [space] » dopasować przestrzeń pod /x może być nieco szybciej w Ruby. (Są takie same w ostatnich wersjach Perla.) Użyłem tego drugiego, ponieważ jest znacznie bardziej czytelny.

+0

@xpapad, poprawiono moją odpowiedź. – ikegami

1

Ruby

File.open(ARGV.shift).each do |line| 
    if line =~ /(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/ 
     puts "#{$1}: #{$2}" 
    end 
end 

Zmiana match metodą =~ operatora. Jest szybszy, ponieważ:

(Ruby ma Benchmark nie wiem zawartości plików więc przypadkowo wpisane coś.) Sprawozdanie

require 'benchmark' 

def bm(n) 
    Benchmark.bm do |x| 
    x.report{n.times{"asdfajdfaklsdjfklajdklfj".match(/fa/)}} 
    x.report{n.times{"asdfajdfaklsdjfklajdklfj" =~ /fa/}} 
    x.report{n.times{/fa/.match("asdfajdfaklsdjfklajdklfj")}} 
    end 
end 

bm(100000) 

wyjściowa:

 user  system  total  real 
    0.141000 0.000000 0.141000 ( 0.140564) 
    0.047000 0.000000 0.047000 ( 0.046855) 
    0.125000 0.000000 0.125000 ( 0.124945) 

środkowy jest za pomocą =~. Zajmuje mniej niż 1/3 innych. Pozostałe dwie metody to: match. Tak więc użyj kodu =~.

+0

Próbowałem = ~ zamiast dopasowania, bez zmian w wydajności. – xpapad

1

Dopasowywanie wyrażenia regularnego jest czasochłonne w porównaniu do innych form dopasowywania. Ponieważ oczekujesz długiego, statycznego ciągu w środku pasujących linii, spróbuj odfiltrować linie, które nie zawierają tego ciągu, używając stosunkowo tanich operacji na ciągach. To powinno spowodować mniej, że musi przejść przez parsowanie wyrażeń regularnych (w zależności od tego, jak wyglądają twoje dane wejściowe, oczywiście).

f = File.open(ARGV.shift) 
my_re = Regexp.new(/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) 
while (line = f.gets) 
    continue if line.index('SENDING REQUEST') == nil 
    if my_re.match(line) 
     puts "#{$1}: #{$2}" 
    end 
end 
f.close() 

Nie testowałem tej konkretnej wersji, ponieważ nie posiadam danych wejściowych. W przeszłości robiłem takie rzeczy, szczególnie w przypadku długich plików logów, w których filtrowanie wstępne może wyeliminować większość danych wejściowych bez uruchamiania żadnych wyrażeń regularnych.

2

Spróbuj użyć rozszerzenia (?>re). Zobacz Ruby-Documentation Szczegółowe tu cytuję:

Konstrukt [..] hamuje wycofywania, które mogą być wzmocnienie wydajność. Na przykład wzorzec wykładniczy przyjmuje czas wykładniczy, gdy jest dopasowany do ciągu zawierającego ciąg znaków, po którym następują liczby b s, ale bez końcowych a. Jednak można tego uniknąć, stosując zagnieżdżone wyrażenie regularne: /a(?>.*b).*a/.

File.open(ARGV.shift) do |f| 
    while line = f.gets 
    if /(.*?)(?> \|.*?SENDING REQUEST.*?TID=)(.*?),/.match(line) 
     puts "#{$1}: #{$2}" 
    end 
    end 
end