24

Ruby (1.9) スクリプトで極端な遅延が発生していることに気付き、掘り下げた結果、正規表現のマッチングに行き着きました。Perl と Ruby で次のテスト スクリプトを使用しています。

パール:

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

ルビー:

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

両方のスクリプトに同じ入力 (44290 行しかないファイル) を使用します。それぞれのタイミングは次のとおりです。

パール:

xenofon@cpm:~/bin/local/project$ time ./try.pl input >/dev/null

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

ルビー:

xenofon@cpm:~/bin/local/project$ time ./try.rb input >/dev/null

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

私はひどく愚かなことをしていると思います、何か提案はありますか?

ありがとうございました

4

6 に答える 6

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

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

または

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

f = File.open( ARGV.shift )
f.each_line do |line|
  if regex.match(line)
    puts "#{$1}: #{$2}"
  end
于 2012-04-20T09:28:39.760 に答える
5

考えられる違いの 1 つは、実行されるバックトラッキングの量です。Perl は、バックトラック (つまり、パターンの一部が一致しない可能性がある場合に気付く) の際に、検索ツリーを剪定するというより良い仕事をするかもしれません。その正規表現エンジンは高度に最適化されています。

まず、先頭に « » を追加する^と、大きな違いが生じる可能性があります。パターンが位置 0 から一致しない場合、開始位置 1 でも一致しません! したがって、位置 1 で一致させようとしないでください。

同様に、« .*?» はあなたが思っているほど制限的ではなく、その各インスタンスをより制限的なパターンに置き換えることで、後戻りを防ぐことができます。

試してみませんか:

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

.*?(最初の « » を « »に置き換えても安全かどうかわからない[^|]ので、そうしませんでした。)

(少なくとも、1 つの文字列に一致するパターンの場合(?:(?!PAT).)は、 to PATas [^CHAR]is toCHARです。)

« » が改行との一致を許可されている場合、使用/sすると速度が向上する可能性があり.ますが、それはかなりマイナーだと思います。

« » の代わりに « » を使用して下のスペースに一致させると、Ruby ではわずかに高速になる可能性があります。(Perl の最近のバージョンでも同じです。) 私は後者を使用しました。\space[space]/x

于 2012-04-20T18:57:56.937 に答える
5

perlretutの章から: Perlセクションでの正規表現の使用 - 「検索と置換」

(正規表現はループの中に現れますが、Perl はそれを一度だけコンパイルするほど賢いです。)

Ruby についてはよくわかりませんが、各サイクルで正規表現をコンパイルするのではないかと思います。
(LaGrandMereの回答のコードを試して確認してください)。

于 2012-04-20T09:48:29.610 に答える
2

(?>re)拡張機能を使用してみてください。詳細については、 Ruby ドキュメントを参照してください。ここに引用があります。

この構造 [..] はバックトラッキングを抑制し、パフォーマンスを向上させます。たとえば、の 後にいくつかの が続き、末尾に がない/a.*b.*a/文字列に対してパターンを照合すると、指数関数的な時間がかかります。ただし、これはネストされた正規表現を使用することで回避できます 。aba/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
于 2013-04-10T14:18:22.577 に答える
1

正規表現のマッチングは、他の形式のマッチングと比較して時間がかかります。一致する行の途中に長く静的な文字列が必要なため、比較的安価な文字列操作を使用して、その文字列を含まない行を除外してみてください。これにより、正規表現の解析を行う必要が少なくなります(もちろん、入力がどのように見えるかによって異なります)。

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()

入力データがないため、この特定のバージョンのベンチマークは行っていません。私は過去にこのようなことを成功させてきましたが、特に、事前フィルタリングによって正規表現を実行せずに入力の大部分を削除できる長いログファイルを使用しました。

于 2012-04-20T20:19:00.650 に答える
1

ルビー:

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

matchメソッドを=~演算子に変更します。次の理由により高速です。

(RubyにはBenchmarkがあります。ファイルの内容がわからないので、ランダムに入力しました)

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)

出力レポート:

       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)

真ん中が使って=~います。他社の1/3以下で済みます。他の 2 つはmatchメソッドを使用しています。したがって、=~コードで使用してください。

于 2012-04-20T19:33:39.530 に答える