文字列がルビの正規表現と一致するかどうかを確認する最も速い方法は?


95

Rubyで文字列が正規表現に一致するかどうかを確認する最も速い方法は何ですか?

私の問題は、実行時に与えられる正規表現に一致するものを見つけるために、文字列の膨大なリストを「egrep」する必要があることです。文字列が正規表現と一致するかどうかだけに注意します。一致する場所や、一致するグループの内容は関係ありません。この仮定を使用して、コードが正規表現のマッチングに費やす時間を削減できることを願っています。

私は正規表現をロードします

pattern = Regexp.new(ptx).freeze

私はそれstring =~ patternがより少し速いことを発見しましたstring.match(pattern)

このテストをさらに速くするために使用できる他のトリックやショートカットはありますか?


一致するグループのコンテンツを気にしない場合、なぜそれらを持っているのですか?それらを非キャプチャに変換することで、正規表現を高速化できます。
マークトーマス

1
regexpは実行時に提供されるため、私はそれが制約されていないことを前提としています。この場合、reg-exp内にグループへの内部参照があるため、正規表現を変更して非キャプチャに変換すると、結果が変更される可能性があります(ただし、さらに内部参照をチェックしますが、問題はますます複雑になります)。私は好奇心が強い=〜はstring.matchよりも速いでしょう。
djconnel 2012

ここで正規表現を凍結する利点は何ですか?
Hardik 2015

回答:


103

Ruby 2.4.0以降では、次のものを使用できますRegExp#match?

pattern.match?(string)

Regexp#match?およびのような他のメソッドによって実行されるオブジェクト割り当てを回避するため、2.4.0リリースノートでは、パフォーマンスの強化として明示的にリストされています。Regexp#match=~

Regexp#match?
を追加しましたRegexp#match?。これは、後方参照オブジェクトを作成したり、$~オブジェクト割り当てを減らすために変更したりせずに、正規表現一致を実行します。


5
提案ありがとうございます。ベンチマークスクリプトを更新しましたRegexp#match?が、確かに他の選択肢より少なくとも50%高速です。
ジョエレ

74

これは簡単なベンチマークです:

require 'benchmark'

"test123" =~ /1/
=> 4
Benchmark.measure{ 1000000.times { "test123" =~ /1/ } }
=>   0.610000   0.000000   0.610000 (  0.578133)

"test123"[/1/]
=> "1"
Benchmark.measure{ 1000000.times { "test123"[/1/] } }
=>   0.718000   0.000000   0.718000 (  0.750010)

irb(main):019:0> "test123".match(/1/)
=> #<MatchData "1">
Benchmark.measure{ 1000000.times { "test123".match(/1/) } }
=>   1.703000   0.000000   1.703000 (  1.578146)

だから、=~速いですが、それはあなたが、戻り値として持っていたいものによって決まります。テキストに正規表現が含まれているかどうかを確認するだけの場合は、=~


2
私が書いたように、私はすでにそれ=~がよりも速く、よりmatch大きな正規表現で動作するときの劇的なパフォーマンスの向上が少ないことを発見しました。私が思っているのは、このチェックをさらに高速化するための奇妙な方法があるかどうかです。おそらく、Regexpの奇妙なメソッドや奇妙な構成を利用しています。
gioele 2012

他の解決策はないと思います
Dougui

どう!("test123" !~ /1/)ですか?
ma11hew28

1
@ MattDiPasquale、2倍の逆数はこれより速くてはなりません"test123" =~ /1/
Dougui

1
/1/.match?("test123")"test123" =~ /1/テキストに正規表現が含まれているかどうかを確認するだけの場合よりも高速です。
noraj 2017

41

これは、ネット上でいくつかの記事を見つけた後に実行したベンチマークです。

2.4.0での勝者はre.match?(str)(@wiktor-stribiżewによって提案されたとおり)、以前のバージョンでre =~ strは最速のようstr =~ reですが、ほぼ同じです。

#!/usr/bin/env ruby
require 'benchmark'

str = "aacaabc"
re = Regexp.new('a+b').freeze

N = 4_000_000

Benchmark.bm do |b|
    b.report("str.match re\t") { N.times { str.match re } }
    b.report("str =~ re\t")    { N.times { str =~ re } }
    b.report("str[re]  \t")    { N.times { str[re] } }
    b.report("re =~ str\t")    { N.times { re =~ str } }
    b.report("re.match str\t") { N.times { re.match str } }
    if re.respond_to?(:match?)
        b.report("re.match? str\t") { N.times { re.match? str } }
    end
end

結果MRI 1.9.3-o551:

$ ./bench-re.rb  | sort -t $'\t' -k 2
       user     system      total        real
re =~ str         2.390000   0.000000   2.390000 (  2.397331)
str =~ re         2.450000   0.000000   2.450000 (  2.446893)
str[re]           2.940000   0.010000   2.950000 (  2.941666)
re.match str      3.620000   0.000000   3.620000 (  3.619922)
str.match re      4.180000   0.000000   4.180000 (  4.180083)

結果MRI 2.1.5:

$ ./bench-re.rb  | sort -t $'\t' -k 2
       user     system      total        real
re =~ str         1.150000   0.000000   1.150000 (  1.144880)
str =~ re         1.160000   0.000000   1.160000 (  1.150691)
str[re]           1.330000   0.000000   1.330000 (  1.337064)
re.match str      2.250000   0.000000   2.250000 (  2.255142)
str.match re      2.270000   0.000000   2.270000 (  2.270948)

結果MRI 2.3.3(正規表現マッチングに回帰があるようです):

$ ./bench-re.rb  | sort -t $'\t' -k 2
       user     system      total        real
re =~ str         3.540000   0.000000   3.540000 (  3.535881)
str =~ re         3.560000   0.000000   3.560000 (  3.560657)
str[re]           4.300000   0.000000   4.300000 (  4.299403)
re.match str      5.210000   0.010000   5.220000 (  5.213041)
str.match re      6.000000   0.000000   6.000000 (  6.000465)

結果MRI 2.4.0:

$ ./bench-re.rb  | sort -t $'\t' -k 2
       user     system      total        real
re.match? str     0.690000   0.010000   0.700000 (  0.682934)
re =~ str         1.040000   0.000000   1.040000 (  1.035863)
str =~ re         1.040000   0.000000   1.040000 (  1.042963)
str[re]           1.340000   0.000000   1.340000 (  1.339704)
re.match str      2.040000   0.000000   2.040000 (  2.046464)
str.match re      2.180000   0.000000   2.180000 (  2.174691)

補足すると、リテラル形式はこれらより高速です。例えば/a+b/ =~ strstr =~ /a+b/。関数を介してそれらを反復する場合でも有効です。これは、変数の正規表現を保存およびフリーズするよりも優れていると考えるのに十分有効であると思います。ruby 1.9.3p547、ruby 2.0.0p481、ruby 2.1.4p265でスクリプトをテストしました。これらの改善が後のパッチで行われた可能性はありますが、以前のバージョン/パッチでまだテストする予定はありません。
konsolebox 2014年

!(re !~ str)はもっと速いかもしれないと思ったが、それはそうではない。
ma11hew28

7

どの程度re === str(ケース比較)?

これはtrueまたはfalseに評価され、一致を格納し、一致インデックスなどを返す必要がないため、を使用するよりもさらに高速な一致方法になるかどうか疑問に思い=~ます。


OK、これをテストしました。=~複数のキャプチャグループがあっても、さらに高速ですが、他のオプションより高速です。

ところで、何が良いですfreezeか?それによるパフォーマンスの向上は測定できませんでした。


の影響はfreezeベンチマークループの前に発生し、パターン自体に作用するため、結果には表示されません。
Tin Man、

4

正規表現の複雑さに応じて、単純な文字列スライスを使用できます。私はあなたのアプリケーションにとってこれの実用性について、またはそれが実際に速度の改善を提供するかどうかについてはわかりません。

'testsentence'['stsen']
=> 'stsen' # evaluates to true
'testsentence'['koala']
=> nil # evaluates to false

regexpは実行時に提供され、それを制御できないため、文字列のスライスを使用できません。
gioele 2012

固定文字列を使用したスライスではなく、文字列スライスを使用できます。引用符で囲んだ文字列の代わりに変数を使用しても、引き続き機能します。
Tin Man

3

私が思っているのは、このチェックをさらに高速化するための奇妙な方法があるかどうかです。おそらく、Regexpの奇妙なメソッドや奇妙な構成を利用しています。

正規表現エンジンは、検索の実装方法が異なりますが、一般に、パターンを高速化するために固定し、特に長い文字列を検索する場合は貪欲な一致を避けます。

特定のエンジンの動作に慣れるまでは、ベンチマークを実行してアンカーを追加/削除し、検索を制限し、ワイルドカードを使用するか、明示的に一致させるかなどを行うのが最善です。

フルーティーな、それはスマートだから宝石は、すぐに物事をベンチマークするのに非常に有用です。Rubyの組み込みのベンチマークコードも役立ちますが、注意しないとだまされるテストを作成することもできます。

ここではStack Overflowの多くの回答で両方を使用しているので、私の回答を検索すると、多くの小さなトリックや結果が表示され、より高速なコードの記述方法がわかります。

覚えておくべき最大のことは、スローダウンが発生する場所を知る前にコードを時期尚早に最適化することは悪いことです。


0

完全にWiktor第StribiżewDouguiは、私はと言うでしょう答える/regex/.match?("string")程度速いほど"string".match?(/regex/)

Ruby 2.4.0(10000 000〜2秒)

2.4.0 > require 'benchmark'
 => true 
2.4.0 > Benchmark.measure{ 10000000.times { /^CVE-[0-9]{4}-[0-9]{4,}$/.match?("CVE-2018-1589") } }
 => #<Benchmark::Tms:0x005563da1b1c80 @label="", @real=2.2060338060000504, @cstime=0.0, @cutime=0.0, @stime=0.04000000000000001, @utime=2.17, @total=2.21> 
2.4.0 > Benchmark.measure{ 10000000.times { "CVE-2018-1589".match?(/^CVE-[0-9]{4}-[0-9]{4,}$/) } }
 => #<Benchmark::Tms:0x005563da139eb0 @label="", @real=2.260814556000696, @cstime=0.0, @cutime=0.0, @stime=0.010000000000000009, @utime=2.2500000000000004, @total=2.2600000000000007> 

Ruby 2.6.2(100 000 000〜20秒)

irb(main):001:0> require 'benchmark'
=> true
irb(main):005:0> Benchmark.measure{ 100000000.times { /^CVE-[0-9]{4}-[0-9]{4,}$/.match?("CVE-2018-1589") } }
=> #<Benchmark::Tms:0x0000562bc83e3768 @label="", @real=24.60139879199778, @cstime=0.0, @cutime=0.0, @stime=0.010000999999999996, @utime=24.565644999999996, @total=24.575645999999995>
irb(main):004:0> Benchmark.measure{ 100000000.times { "CVE-2018-1589".match?(/^CVE-[0-9]{4}-[0-9]{4,}$/) } }
=> #<Benchmark::Tms:0x0000562bc846aee8 @label="", @real=24.634255946999474, @cstime=0.0, @cutime=0.0, @stime=0.010046, @utime=24.598276, @total=24.608321999999998>

注:時間は変動し、場合によって/regex/.match?("string")はより速くなり、時には"string".match?(/regex/)、マシンの活動によるものである可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.