Самый быстрый способ проверить, соответствует ли строка регулярному выражению в ruby?
Каков самый быстрый способ проверить, соответствует ли строка регулярному выражению в Ruby?
Моя проблема в том, что я должен "egrep" через огромный список строк, чтобы найти те, которые соответствуют регулярному выражению, которое дается во время выполнения. Мне только заботится о том, соответствует ли строка регулярному выражению, а не по его совпадению, и какому содержимому соответствует группа.. Я надеюсь, что это предположение может быть использовано для сокращения времени, затрачиваемого моим кодом регэкспы.
Я загружаю regexp с помощью
pattern = Regexp.new(ptx).freeze
Я обнаружил, что string =~ pattern
немного быстрее, чем string.match(pattern)
.
Существуют ли другие трюки или ярлыки, которые могут использоваться для ускорения этого теста?
Ответы
Ответ 1
Начиная с Ruby 2.4.0, вы можете использовать RegExp#match?
:
pattern.match?(string)
RegExp#match?
явно указан как повышение производительности в примечаниях к выпуску для версии 2.4.0, поскольку он позволяет избежать распределения объектов, выполняемых другими методами таких как Regexp#match
и =~
:
Regexp # матч?
Добавлен RegExp#match?
, который выполняет соответствие регулярному выражению без создания обратного ссылочного объекта и изменения $~
, чтобы уменьшить выделение объектов.
Ответ 2
Это простой тест:
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)
Итак, =~
работает быстрее, но это зависит от того, что вы хотите иметь в качестве возвращаемого значения. Если вы просто хотите проверить, содержит ли текст регулярное выражение или не использовать =~
Ответ 3
Это контрольный показатель, который я выполнил после того, как нашел некоторые статьи в сети.
С 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)
Ответ 4
Как насчет re === str
(сравнение случаев)?
Так как он оценивает значение true или false и не нуждается в сохранении совпадений, возвращении индекса соответствия и того материала, мне интересно, будет ли он еще более быстрым способом сопоставления, чем =~
.
Хорошо, я проверил это. =~
все еще быстрее, даже если у вас несколько групп захвата, однако это быстрее, чем другие параметры.
Кстати, что хорошего freeze
? Я не мог измерить прирост производительности.
Ответ 5
В зависимости от того, насколько сложным является ваше регулярное выражение, вы можете просто использовать простую секцию строк. Я не уверен в практичности этого для вашего приложения или действительно ли он будет предлагать какие-либо улучшения скорости.
'testsentence'['stsen']
=> 'stsen' # evaluates to true
'testsentence'['koala']
=> nil # evaluates to false
Ответ 6
Мне интересно, есть ли какой-нибудь странный способ сделать эту проверку еще быстрее, возможно, используя какой-то странный метод в Regexp или какую-нибудь странную конструкцию.
Двигатели Regexp отличаются тем, как они реализуют поиск, но, в общем, привязывают ваши шаблоны к скорости и избегают жадных совпадений, особенно при поиске длинных строк.
Лучшее, что нужно сделать, пока вы не знакомы с тем, как работает конкретный движок, - это делать тесты и добавлять/удалять привязки, пытаться ограничить поиск, использовать подстановочные знаки против явных совпадений и т.д.
Fruity драгоценный камень очень полезен для быстрого бенчмаркинга, потому что он умный. Ruby, встроенный Benchmark, также полезен, хотя вы можете писать тесты, которые вас обманывают, не будучи осторожными.
Я использовал как много ответов здесь, так и в разделе "Переполнение стека", поэтому вы можете выполнять поиск по моим ответам и получать множество небольших трюков и результатов, чтобы дать вам представление о том, как писать более быстрый код.
Самое важное, что нужно помнить, - это плохо, преждевременно оптимизировать свой код, прежде чем вы узнаете, где происходит замедление.
Ответ 7
Чтобы завершить ответы Виктора Стрибиева и Дуги, я бы сказал, что /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 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/)
, различия могут быть связаны только с "string".match?(/regex/)
компьютера.