it-swarm.com.de

Der schnellste Weg, um zu überprüfen, ob eine Zeichenfolge mit einem Regex übereinstimmt oder nicht in Ruby?

Wie kann ich am schnellsten überprüfen, ob eine Zeichenfolge mit einem regulären Ausdruck in Ruby übereinstimmt?

Mein Problem ist, dass ich durch eine riesige Liste von Strings "egrep" muss, um herauszufinden, welche derjenigen sind, die mit einem Regex übereinstimmen, der zur Laufzeit angegeben wird. Ich kümmere mich nur darum, ob die Zeichenfolge mit dem regulären Ausdruck übereinstimmt, nicht wo sie übereinstimmt, noch was der Inhalt der übereinstimmenden Gruppen ist. Ich hoffe, dass diese Annahme verwendet werden kann, um die Zeit zu reduzieren, die mein Code für das Übereinstimmen von Regex benötigt.

Ich lade den Regex mit

pattern = Regexp.new(ptx).freeze

Ich habe festgestellt, dass string =~ pattern etwas schneller ist als string.match(pattern).

Gibt es andere Tricks oder Tastenkombinationen, mit denen dieser Test noch schneller gemacht werden kann?

64
gioele

Ab Ruby 2.4.0 können Sie RegExp#match? verwenden:

pattern.match?(string)

Regexp#match? ist explizit als Leistungsverbesserung in den Versionshinweisen für 2.4.0 aufgeführt, da Objektzuordnungen vermieden werden, die von anderen Methoden wie Regexp#match und =~ ausgeführt werden:

Regexp # übereinstimmen?
Regexp#match? wurde hinzugefügt, wodurch ein regexp-Abgleich ausgeführt wird, ohne ein hinteres Referenzobjekt zu erstellen und $~ zu ändern, um die Objektzuordnung zu reduzieren.

62

Dies ist ein einfacher Benchmark:

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)

=~ ist also schneller, hängt jedoch davon ab, was Sie als Rückgabewert haben möchten. Wenn Sie nur prüfen möchten, ob der Text einen Regex enthält oder nicht, verwenden Sie =~.

66
Dougui

Dies ist der Benchmark, den ich gelaufen bin, nachdem ich einige Artikel im Netz gefunden habe. 

Mit 2.4.0 ist der Gewinner re.match?(str) (wie von @ wiktor-stribiżew vorgeschlagen), in früheren Versionen scheint re =~ str am schnellsten zu sein, obwohl str =~ re fast genauso schnell ist.

#!/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

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

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

Ergebnisse MRI 2.3.3 (es scheint eine Regression im Regex-Matching zu geben):

$ ./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)

Ergebnisse 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)
34
gioele

Was ist mit re === str (Fallvergleich)?

Da es als wahr oder falsch ausgewertet wird und keine Übereinstimmungen gespeichert werden müssen, den Übereinstimmungsindex und das Zeug zurückgeben, frage ich mich, ob dies eine noch schnellere Methode des Zuordnens wäre als =~.


Ok, ich habe das getestet. =~ ist immer noch schneller, auch wenn Sie mehrere Capture-Gruppen haben, jedoch schneller als die anderen Optionen.

Übrigens, was nützt freeze? Ich konnte keinen Leistungsschub daraus messen.

6
Heiko

Je nachdem, wie kompliziert Ihr regulärer Ausdruck ist, können Sie möglicherweise nur ein einfaches Zeichenfolgen-Slicing verwenden. Ich bin nicht sicher, ob dies für Ihre Anwendung praktikabel ist oder ob es tatsächlich Geschwindigkeitsverbesserungen bietet oder nicht.

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

Ich frage mich, ob es einen seltsamen Weg gibt, diese Überprüfung noch schneller zu machen, vielleicht eine seltsame Methode in Regexp oder ein komisches Konstrukt auszunutzen. 

Regexp Engines unterscheiden sich in der Art und Weise, wie Suchvorgänge durchgeführt werden. Im Allgemeinen verankern Sie Ihre Muster jedoch in Bezug auf Geschwindigkeit und vermeiden gierige Übereinstimmungen, insbesondere bei der Suche nach langen Zeichenfolgen.

Das Beste, was Sie tun können, bis Sie mit der Funktionsweise einer bestimmten Engine vertraut sind, besteht darin, Benchmarks durchzuführen und Anker hinzuzufügen/zu entfernen, die Suche einzuschränken, Platzhalter gegen explizite Übereinstimmungen zu verwenden usw.

Der Fruity - Edelstein ist sehr nützlich für schnelles Benchmarking, weil er intelligent ist. Rubys integrierter Benchmark - Code ist ebenfalls nützlich, obwohl Sie Tests schreiben können, die Sie täuschen, wenn Sie nicht vorsichtig sind.

Ich habe beides in vielen Antworten hier bei Stack Overflow verwendet, sodass Sie meine Antworten durchsuchen können und viele kleine Tricks und Ergebnisse sehen werden, um Ihnen Anregungen zu geben, wie Sie schnelleren Code schreiben können.

Das Wichtigste ist, dass Sie Ihren Code vorzeitig nicht optimieren können, bevor Sie wissen, wo die Verlangsamung auftritt. 

2
the Tin Man

Um die Antworten von Wiktor Stribiżew und Dougui zu vervollständigen, würde ich sagen, dass /regex/.match?("string") etwas schneller ist als "string".match?(/regex/).

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> 
0
noraj