文字列がRubyの正規表現に一致するかどうかを確認する最も速い方法は何ですか?
私の問題は、実行時に与えられる正規表現に一致するものを見つけるために、文字列の膨大なリストを「egrep」しなければならないことです。 文字列が正規表現に一致するかどうかだけを気にし、一致する場所や一致するグループの内容は気にしません。この仮定を使用して、コードの照合に費やす時間を削減できることを願っています正規表現。
正規表現をロードします
pattern = Regexp.new(ptx).freeze
string =~ pattern
はstring.match(pattern)
よりもわずかに速いことがわかりました。
このテストをさらに高速化するために使用できる他のトリックやショートカットはありますか?
これは簡単なベンチマークです。
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.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)
re === str
(ケース比較)はどうですか?
Trueまたはfalseに評価され、一致を格納し、一致インデックスなどを返す必要がないので、=~
よりもさらに高速な一致方法になるのではないかと思います。
OK、これをテストしました。複数のキャプチャグループがある場合でも、=~
は依然として高速ですが、他のオプションよりも高速です。
ところで、freeze
とは何ですか?パフォーマンスの向上を測定できませんでした。
正規表現の複雑さに応じて、単純な文字列スライスを使用することもできます。あなたのアプリケーションに対するこの実用性について、または実際に速度の改善を提供するかどうかはわかりません。
'testsentence'['stsen']
=> 'stsen' # evaluates to true
'testsentence'['koala']
=> nil # evaluates to false
私が疑問に思っているのは、このチェックをさらに速くするための奇妙な方法があれば、Regexpの奇妙な方法や奇妙な構造を悪用するかもしれないということです。
正規表現エンジンは検索の実装方法が異なりますが、一般に、特に長い文字列を検索する場合は、パターンを高速化して貪欲な一致を回避します。
特定のエンジンがどのように機能するかを理解するまで、ベストなことは、ベンチマークを行い、アンカーを追加/削除し、検索を制限し、ワイルドカードと明示的な一致を使用するなどです。
Fruity gemは、物事をすばやくベンチマークするのに非常に便利です。スマートだからです。 Rubyに組み込まれている Benchmark コードも便利ですが、注意を怠ることでだまされるテストを作成できます。
ここではStack Overflowの多くの回答で両方を使用しているため、回答を検索すると、多くの小さなトリックや結果が表示され、より高速なコードを記述する方法のアイデアが得られます。
覚えておくべき最大のことは、スローダウンが発生する場所を知る前にコードを時期尚早に最適化することは悪いことです。
WiktorStribiżewおよびDouguiの回答を完了するには、/regex/.match?("string")
とほぼ同じくらい速い"string".match?(/regex/)
と答えます。
Ruby 2.4.0(10,000 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〜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/)
になりますが、違いはマシンのアクティビティのみに起因する可能性があります。