Cách nhanh nhất để kiểm tra xem một chuỗi có khớp với regexp trong ruby ​​không?


97

Cách nhanh nhất để kiểm tra xem một chuỗi có khớp với một biểu thức chính quy trong Ruby không?

Vấn đề của tôi là tôi phải "egrep" thông qua một danh sách khổng lồ các chuỗi để tìm những chuỗi nào phù hợp với regexp được đưa ra trong thời gian chạy. Tôi chỉ quan tâm đến việc liệu chuỗi có khớp với regexp hay không, chứ không phải nơi nó khớp, cũng như nội dung của các nhóm khớp là gì. Tôi hy vọng giả định này có thể được sử dụng để giảm lượng thời gian mã của tôi dành để khớp với các regexps.

Tôi tải regexp với

pattern = Regexp.new(ptx).freeze

Tôi đã tìm thấy rằng string =~ patternhơi nhanh hơn string.match(pattern).

Có thủ thuật hoặc phím tắt nào khác có thể được sử dụng để làm cho quá trình kiểm tra này nhanh hơn không?


Nếu bạn không quan tâm đến nội dung của các nhóm phù hợp, tại sao bạn lại có chúng? Bạn có thể làm cho regex nhanh hơn bằng cách chuyển đổi chúng thành không chụp.
Mark Thomas

1
Vì regexp được cung cấp tại thời điểm chạy, tôi cho rằng nó không bị giới hạn, trong trường hợp đó có thể có các tham chiếu nội bộ trong reg-exp thành các nhóm và do đó, việc chuyển đổi chúng thành không ghi lại bằng cách sửa đổi regexp có thể sửa đổi kết quả (trừ khi bạn kiểm tra thêm các tài liệu tham khảo nội bộ, nhưng vấn đề ngày càng trở nên phức tạp). Tôi thấy nó tò mò = ~ sẽ nhanh hơn string.match.
djconnel

lợi ích của việc đóng băng regexp ở đây là gì?
Hardik

Câu trả lời:


103

Bắt đầu với Ruby 2.4.0, bạn có thể sử dụng RegExp#match?:

pattern.match?(string)

Regexp#match?được liệt kê rõ ràng là một cải tiến hiệu suất trong ghi chú phát hành cho 2.4.0 , vì nó tránh phân bổ đối tượng được thực hiện bởi các phương pháp khác như Regexp#match=~:

Regexp # phù hợp?
Đã thêm Regexp#match?, thực thi đối sánh regexp mà không tạo đối tượng tham chiếu ngược và thay đổi $~để giảm phân bổ đối tượng.


5
Cảm ơn vì đã góp ý. Tôi đã cập nhật tập lệnh điểm chuẩn và Regexp#match?thực sự nhanh hơn ít nhất 50% so với các lựa chọn thay thế khác.
gioele 17/03/17

74

Đây là một tiêu chuẩn đơn giản:

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)

Vì vậy, =~nhanh hơn nhưng nó phụ thuộc vào những gì bạn muốn có dưới dạng giá trị trả về. Nếu bạn chỉ muốn kiểm tra xem văn bản có chứa regex hay không, hãy sử dụng=~


2
Như tôi đã viết, tôi đã thấy rằng =~nó nhanh hơn match, với sự gia tăng hiệu suất ít kịch tính hơn khi hoạt động trên regexps lớn hơn. Điều tôi đang thắc mắc là liệu có cách nào kỳ lạ để thực hiện việc kiểm tra này nhanh hơn không, có thể khai thác một số phương pháp kỳ lạ trong Regexp hoặc một số cấu trúc kỳ lạ.
gioele

Tôi nghĩ không có giải pháp nào khác
Dougui

Về !("test123" !~ /1/)thì sao?
ma11hew28

1
@MattDiPasquale, hai lần nghịch đảo không được nhanh hơn"test123" =~ /1/
Dougui

1
/1/.match?("test123")nhanh hơn "test123" =~ /1/nếu chỉ để kiểm tra xem văn bản có chứa regex hay không.
noraj

42

Đây là điểm chuẩn mà tôi đã chạy sau khi tìm thấy một số bài báo trên mạng.

Với 2.4.0, người chiến thắng là re.match?(str)(theo gợi ý của @ wiktor-stribiżew), trên các phiên bản trước, re =~ strcó vẻ là nhanh nhất, mặc dù str =~ regần như nhanh.

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

Kết quả 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)

Kết quả 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)

Kết quả MRI 2.3.3 (có vẻ như có sự hồi quy trong kết hợp 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)

Kết quả 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)

Chỉ cần thêm ghi chú, các dạng chữ nhanh hơn các dạng này. Ví dụ: /a+b/ =~ strstr =~ /a+b/. Nó hợp lệ ngay cả khi lặp lại chúng qua các hàm và tôi thấy điều này đủ hợp lệ để coi là tốt hơn việc lưu trữ và đóng băng các biểu thức chính quy trên một biến. Tôi đã thử nghiệm tập lệnh của mình với ruby ​​1.9.3p547, ruby ​​2.0.0p481 và ruby ​​2.1.4p265. Có thể những cải tiến này đã được thực hiện trên các bản vá lỗi sau này nhưng tôi chưa có kế hoạch kiểm tra nó với các phiên bản / bản vá lỗi trước đó.
konsolebox

Tôi nghĩ !(re !~ str)có thể nhanh hơn, nhưng không phải vậy.
ma11hew28

7

Thế còn re === str (so sánh trường hợp) thì sao?

Vì nó đánh giá là true hoặc false và không cần lưu trữ các kết quả phù hợp, trả về chỉ mục đối sánh và những thứ đó, tôi tự hỏi liệu nó có phải là cách so khớp nhanh hơn không =~.


Ok, tôi đã thử nghiệm cái này. =~vẫn nhanh hơn, ngay cả khi bạn có nhiều nhóm chụp, tuy nhiên nó nhanh hơn các tùy chọn khác.

BTW, tốt là freezegì? Tôi không thể đo lường bất kỳ mức tăng hiệu suất nào từ nó.


Các hiệu ứng của freezesẽ không hiển thị trong kết quả bởi vì nó xảy ra trước các vòng lặp điểm chuẩn và tác động lên chính mẫu.
the Tin Man

5

Tùy thuộc vào mức độ phức tạp của biểu thức chính quy, bạn có thể chỉ cần sử dụng cách cắt chuỗi đơn giản. Tôi không chắc về tính thực tế của điều này đối với ứng dụng của bạn hoặc liệu nó có thực sự cung cấp bất kỳ cải tiến tốc độ nào hay không.

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

Tôi không thể sử dụng tính năng cắt chuỗi vì regexp được cung cấp tại thời điểm chạy và tôi không có bất kỳ quyền kiểm soát nào đối với điều đó.
gioele

Bạn có thể sử dụng cắt chuỗi, chỉ không cắt bằng chuỗi cố định. Sử dụng một biến thay vì một chuỗi trong dấu ngoặc kép và nó vẫn hoạt động.
the Tin Man

3

Điều tôi đang thắc mắc là liệu có cách nào kỳ lạ để thực hiện việc kiểm tra này nhanh hơn không, có thể khai thác một số phương pháp kỳ lạ trong Regexp hoặc một số cấu trúc kỳ lạ.

Các công cụ Regexp khác nhau về cách chúng triển khai các tìm kiếm, nhưng nói chung, cố định các mẫu của bạn để tăng tốc độ và tránh các kết quả phù hợp tham lam, đặc biệt là khi tìm kiếm các chuỗi dài.

Điều tốt nhất nên làm, cho đến khi bạn quen với cách hoạt động của một công cụ cụ thể, là thực hiện các điểm chuẩn và thêm / xóa các neo, thử giới hạn tìm kiếm, sử dụng ký tự đại diện so với các kết quả phù hợp rõ ràng, v.v.

Các Fruity đá quý là rất hữu ích cho mọi thứ nhanh chóng điểm chuẩn, bởi vì nó là thông minh. Mã Benchmark tích hợp của Ruby cũng rất hữu ích, mặc dù bạn có thể viết các bài kiểm tra đánh lừa bạn nếu không cẩn thận.

Tôi đã sử dụng cả hai trong nhiều câu trả lời ở đây trên Stack Overflow, vì vậy bạn có thể tìm kiếm các câu trả lời của tôi và sẽ thấy rất nhiều thủ thuật và kết quả nhỏ để cung cấp cho bạn ý tưởng về cách viết mã nhanh hơn.

Điều lớn nhất cần nhớ là, thật tệ khi tối ưu hóa quá sớm mã của bạn trước khi bạn biết vị trí xảy ra hiện tượng chậm.


0

Để hoàn thành câu trả lời của Wiktor StribiżewDougui, tôi sẽ nói rằng /regex/.match?("string")nhanh nhất là"string".match?(/regex/) .

Ruby 2.4.0 (10 000 000 ~ 2 giây)

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 giây)

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>

Lưu ý: thời gian thay đổi, đôi khi /regex/.match?("string")nhanh hơn và đôi khi "string".match?(/regex/), sự khác biệt có thể chỉ do hoạt động của máy.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.