2012-04-20 53 views
23

Tôi nhận thấy một số sự chậm trễ nghiêm trọng trong các kịch bản Ruby (1.9) của tôi và sau khi một số đào nó đun sôi xuống để phù hợp với biểu thức chính quy. Tôi đang sử dụng các kịch bản thử nghiệm sau đây trong Perl và Ruby:Biểu thức chính quy - Ruby vs Perl

Perl:

$fname = shift(@ARGV); 
open(FILE, "<$fname"); 
while (<FILE>) { 
    if (/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) { 
     print "$1: $2\n"; 
    } 
} 

Ruby:

f = File.open(ARGV.shift) 
while (line = f.gets) 
    if /(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/.match(line) 
     puts "#{$1}: #{$2}" 
    end 
end 

tôi sử dụng các đầu vào tương tự cho cả hai kịch bản, một chỉ với 44290 dòng. Thời gian cho mỗi người là:

Perl:

[email protected]:~/bin/local/project$ time ./try.pl input >/dev/null 

real 0m0.049s 
user 0m0.040s 
sys  0m0.000s 

Ruby:

[email protected]:~/bin/local/project$ time ./try.rb input >/dev/null 

real 1m5.106s 
user 1m4.910s 
sys  0m0.010s 

Tôi đoán tôi đang làm một cái gì đó hết sức ngu ngốc, bất cứ đề nghị?

Cảm ơn bạn

+2

Bạn đã thử 'nếu dòng = ~ /(.*) \ |?.?.?. * GỬI YÊU CẦU * TID = (*), /'? Điều đó cũng làm việc trong Ruby, tôi sẽ tò mò nếu nó có đặc điểm hiệu suất khác nhau. –

Trả lời

7
regex = Regexp.new(/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) 

f = File.open(ARGV.shift).each do |line| 
    if regex .match(line) 
     puts "#{$1}: #{$2}" 
    end 
end 

Hoặc

regex = Regexp.new(/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) 

f = File.open(ARGV.shift) 
f.each_line do |line| 
    if regex.match(line) 
    puts "#{$1}: #{$2}" 
    end 
+0

+1 Tôi thấy rằng Perl đang làm điều này tự động. – stema

+3

Tôi đã thử đề xuất của bạn, nhưng không có thay đổi, thời gian thực hiện vẫn là 1m5.134s – xpapad

+2

Một vài nitpicks: bạn cần giải phóng bộ mô tả tệp sau khi đã hoàn tất, bằng cách gọi 'close' hoặc bằng cách sử dụng' Tệp .open ('tên tệp') {| tệp | } ', đảm bảo tệp được đóng. Ngoài ra, '/#{...}/' biểu thị chữ 'Regexp'; cuộc gọi 'Regexp.new' là không cần thiết. –

5

Từ perlretut chapter: Using regular expressions in Perl phần - "Tìm kiếm và thay thế"

(Mặc dù biểu hiện thường xuyên xuất hiện trong một vòng lặp, Perl là đủ thông minh để biên dịch nó chỉ một lần.)

Tôi không biết Ruby rất tốt, nhưng tôi nghi ngờ rằng nó biên dịch regex trong mỗi chu kỳ.
(Hãy thử mã từ câu trả lời của LaGrandMere để verfiy nó).

+0

Tôi nghi ngờ điều đó. Có một cú pháp đặc biệt cho nó, vì vậy nó có thể được xây dựng trong giai đoạn phân tích cú pháp ... đó là cách trước vòng lặp. – remram

5

Một sự khác biệt có thể là lượng backtracking đang được thực hiện. Perl có thể thực hiện công việc cắt tỉa cây tìm kiếm tốt hơn khi quay ngược lại (tức là nhận thấy khi một phần của mẫu không thể khớp). Động cơ regex của nó được tối ưu hóa cao.

Trước tiên, thêm « ^ » hàng đầu có thể tạo sự khác biệt lớn. Nếu mẫu không khớp với vị trí 0, nó sẽ không khớp ở vị trí bắt đầu 1! Vì vậy, đừng cố gắng khớp tại vị trí 1.

Cùng các dòng giống nhau, « .*? » không giới hạn như bạn nghĩ, và thay thế từng trường hợp bằng mẫu hạn chế hơn có thể ngăn chặn nhiều hoạt động ngược .

Tại sao bạn không thử:

/ 
    ^
    (.*?)      [ ]\| 
    (?:(?!SENDING[ ]REQUEST).)* SENDING[ ]REQUEST 
    (?:(?!TID=).)*    TID= 
    ([^,]*)      , 
/x 

(Không chắc chắn nếu nó là an toàn để thay thế đầu tiên « .*? » với « [^|] », vì vậy tôi đã không.)

(Ít nhất cho mô hình phù hợp với một chuỗi duy nhất, (?:(?!PAT).)PAT như [^CHAR]CHAR.)

Sử dụng /s có thể có thể điều tốc độ lên nếu « . » được phép để phù hợp với dòng mới, nhưng tôi nghĩ rằng nó khá nhỏ.

Sử dụng « \space » thay vì « [space] » để phù hợp với một không gian dưới /x có thể nhanh hơn một chút trong Ruby. (Chúng giống nhau trong các phiên bản gần đây của Perl.) Tôi đã sử dụng thứ hai vì nó dễ đọc hơn nhiều.

+0

@xpapad, Chỉnh sửa câu trả lời của tôi. – ikegami

1

Ruby:

File.open(ARGV.shift).each do |line| 
    if line =~ /(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/ 
     puts "#{$1}: #{$2}" 
    end 
end 

Thay đổi match phương pháp để =~ điều hành. Nó là nhanh hơn bởi vì:

(Ruby có Benchmark tôi không biết nội dung tập tin của bạn vì vậy tôi gõ ngẫu nhiên một cái gì đó.) Báo cáo

require 'benchmark' 

def bm(n) 
    Benchmark.bm do |x| 
    x.report{n.times{"asdfajdfaklsdjfklajdklfj".match(/fa/)}} 
    x.report{n.times{"asdfajdfaklsdjfklajdklfj" =~ /fa/}} 
    x.report{n.times{/fa/.match("asdfajdfaklsdjfklajdklfj")}} 
    end 
end 

bm(100000) 

Output:

 user  system  total  real 
    0.141000 0.000000 0.141000 ( 0.140564) 
    0.047000 0.000000 0.047000 ( 0.046855) 
    0.125000 0.000000 0.125000 ( 0.124945) 

Một trung là sử dụng =~. Phải mất ít hơn 1/3 số khác. Hai phương pháp khác đang sử dụng phương pháp match. Vì vậy, hãy sử dụng =~ trong mã của bạn.

+0

Tôi đã thử = ~ thay vì khớp, không thay đổi hiệu suất. – xpapad

1

Đối sánh cụm từ thông dụng tốn nhiều thời gian so với các hình thức khớp khác. Vì bạn đang mong đợi một chuỗi dài, tĩnh ở giữa các dòng khớp của bạn, hãy thử lọc ra các dòng không bao gồm chuỗi đó bằng cách sử dụng các hoạt động chuỗi tương đối rẻ. Điều đó sẽ dẫn đến ít hơn mà cần phải đi qua phân tích biểu thức chính quy (tùy thuộc vào những gì đầu vào của bạn trông giống như, tất nhiên).

f = File.open(ARGV.shift) 
my_re = Regexp.new(/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) 
while (line = f.gets) 
    continue if line.index('SENDING REQUEST') == nil 
    if my_re.match(line) 
     puts "#{$1}: #{$2}" 
    end 
end 
f.close() 

Tôi chưa đánh giá phiên bản cụ thể này vì tôi không có dữ liệu đầu vào của bạn. Mặc dù vậy, trước đây tôi đã thành công trong việc thực hiện những việc như thế này, đặc biệt là với các tệp nhật ký dài, nơi việc lọc trước có thể loại bỏ phần lớn đầu vào mà không chạy bất kỳ cụm từ thông dụng nào.

2

Hãy thử sử dụng Tiện ích mở rộng (?>re). Xem Ruby-Documentation để biết chi tiết, tại đây được trích dẫn:

Cấu trúc này [..] ức chế lùi lại, có thể là tăng cường hiệu suất . Ví dụ: mẫu /a.*b.*a/ mất thời gian theo số mũ khi được đối sánh với một chuỗi có chứa a theo sau bởi một số b s, nhưng không có dấu kiểm nào a. Tuy nhiên, có thể tránh được điều này bằng cách sử dụng biểu thức chính quy lồng nhau /a(?>.*b).*a/.

File.open(ARGV.shift) do |f| 
    while line = f.gets 
    if /(.*?)(?> \|.*?SENDING REQUEST.*?TID=)(.*?),/.match(line) 
     puts "#{$1}: #{$2}" 
    end 
    end 
end 
Các vấn đề liên quan