Apa cara tercepat untuk memeriksa apakah sebuah string cocok dengan ekspresi reguler di Ruby?
Masalah saya adalah bahwa saya harus "egrep" melalui daftar besar string untuk menemukan mana yang cocok dengan regexp yang diberikan saat runtime. Saya hanya peduli apakah string cocok dengan regexp, bukan di mana cocok, atau apa konten dari grup yang cocok. Saya berharap asumsi ini dapat digunakan untuk mengurangi jumlah waktu yang dihabiskan kode saya untuk mencocokkan regexps.
Saya memuat regexp dengan
pattern = Regexp.new(ptx).freeze
Saya telah menemukan bahwa string =~ pattern
ini sedikit lebih cepat daripada string.match(pattern)
.
Apakah ada trik atau pintasan lain yang dapat digunakan untuk membuat pengujian ini lebih cepat?
ruby
regex
performance
gioele
sumber
sumber
Jawaban:
Dimulai dengan Ruby 2.4.0, Anda dapat menggunakan
RegExp#match?
:Regexp#match?
secara eksplisit dicantumkan sebagai peningkatan kinerja dalam catatan rilis untuk 2.4.0 , karena ini menghindari alokasi objek yang dilakukan oleh metode lain sepertiRegexp#match
dan=~
:sumber
Regexp#match?
memang setidaknya 50% lebih cepat dari alternatif lainnya.Ini adalah patokan sederhana:
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)
Jadi
=~
lebih cepat tetapi tergantung apa yang ingin Anda miliki sebagai nilai yang dikembalikan. Jika Anda hanya ingin memeriksa apakah teks tersebut mengandung regex atau tidak digunakan=~
sumber
=~
lebih cepat daripadamatch
, dengan peningkatan kinerja yang tidak terlalu dramatis saat beroperasi pada regex yang lebih besar. Apa yang saya ingin tahu adalah apakah ada cara aneh untuk membuat pemeriksaan ini lebih cepat, mungkin mengeksploitasi beberapa metode aneh di Regexp atau beberapa konstruksi aneh.!("test123" !~ /1/)
?"test123" =~ /1/
/1/.match?("test123")
lebih cepat daripada"test123" =~ /1/
jika hanya untuk memeriksa apakah teks tersebut mengandung regex atau tidak.Ini adalah patokan yang saya jalankan setelah menemukan beberapa artikel di internet.
Dengan 2.4.0, pemenangnya adalah
re.match?(str)
(seperti yang disarankan oleh @ wiktor-stribiżew), pada versi sebelumnya,re =~ str
tampaknya menjadi yang tercepat, meskipunstr =~ re
hampir sama cepatnya.#!/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
Hasil 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)
Hasil 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)
Hasil MRI 2.3.3 (tampaknya ada regresi dalam pencocokan regex):
$ ./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)
Hasil 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)
sumber
/a+b/ =~ str
danstr =~ /a+b/
. Ini valid bahkan ketika mengulanginya melalui fungsi dan saya melihat ini cukup valid untuk dianggap lebih baik daripada menyimpan dan membekukan ekspresi reguler pada variabel. Saya menguji skrip saya dengan ruby 1.9.3p547, ruby 2.0.0p481 dan ruby 2.1.4p265. Mungkin saja peningkatan ini dilakukan pada tambalan selanjutnya tetapi saya belum berencana untuk mengujinya dengan versi / tambalan sebelumnya.!(re !~ str)
mungkin lebih cepat, tetapi ternyata tidak.Bagaimana dengan
re === str
(perbandingan kasus)?Karena itu mengevaluasi benar atau salah dan tidak perlu menyimpan kecocokan, mengembalikan indeks kecocokan dan hal-hal itu, saya bertanya-tanya apakah itu akan menjadi cara yang lebih cepat untuk mencocokkan daripada
=~
.Oke, saya menguji ini.
=~
masih lebih cepat, bahkan jika Anda memiliki beberapa grup penangkapan, namun ini lebih cepat daripada opsi lainnya.BTW, apa gunanya
freeze
? Saya tidak dapat mengukur peningkatan kinerja apa pun darinya.sumber
freeze
tidak akan muncul dalam hasil karena terjadi sebelum tolok ukur loop, dan bekerja pada pola itu sendiri.Bergantung pada seberapa rumit ekspresi reguler Anda, Anda mungkin dapat menggunakan pemotongan string sederhana. Saya tidak yakin tentang kepraktisan ini untuk aplikasi Anda atau apakah itu benar-benar menawarkan peningkatan kecepatan atau tidak.
'testsentence'['stsen'] => 'stsen' # evaluates to true 'testsentence'['koala'] => nil # evaluates to false
sumber
Mesin Regexp bervariasi dalam cara mereka mengimplementasikan pencarian, tetapi, secara umum, jangkar pola Anda untuk kecepatan, dan hindari kecocokan serakah, terutama saat mencari string yang panjang.
Hal terbaik untuk dilakukan, sampai Anda terbiasa dengan cara kerja mesin tertentu, adalah melakukan tolok ukur dan menambah / menghapus jangkar, mencoba membatasi pencarian, menggunakan karakter pengganti vs. pencocokan eksplisit, dll.
The Fruity permata ini sangat berguna untuk hal-hal dengan cepat benchmarking, karena itu cerdas. Kode Benchmark bawaan Ruby juga berguna, meskipun Anda dapat menulis tes yang menipu Anda dengan tidak berhati-hati.
Saya telah menggunakan keduanya dalam banyak jawaban di sini di Stack Overflow, sehingga Anda dapat menelusuri jawaban saya dan akan melihat banyak trik dan hasil kecil untuk memberi Anda ide tentang cara menulis kode yang lebih cepat.
Hal terbesar yang perlu diingat adalah, buruk untuk mengoptimalkan kode sebelum waktunya sebelum Anda tahu di mana pelambatan terjadi.
sumber
Untuk melengkapi jawaban Wiktor Stribiżew dan Dougui, saya akan mengatakannya
/regex/.match?("string")
secepat itu"string".match?(/regex/)
.Ruby 2.4.0 (10.000.000 ~ 2 detik)
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 detik)
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>
Catatan: waktu bervariasi, terkadang
/regex/.match?("string")
lebih cepat dan terkadang"string".match?(/regex/)
, perbedaan tersebut mungkin hanya karena aktivitas mesin.sumber