正则表达式 - Ruby vs Perl

问题描述:

我注意到我的Ruby(1.9)脚本中出现了一些极端的延迟,并且经过一些挖掘后,它被归结为正则表达式匹配。我使用Perl和Ruby的下列测试脚本:正则表达式 - Ruby vs Perl

的Perl:

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

红宝石:

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

我用同样的输入这两个脚本,一个只有44290行的文件。 为每一个时机是:

的Perl:

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

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

红宝石:

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

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

我想我做的事情非常愚蠢的,有什么建议?

谢谢

+2

您是否尝试过'如果行=〜/(.*)\ |?。?。?。*发送请求* TID =(*),/'?这也适用于Ruby,如果它具有不同的性能特征,我会很好奇。 – 2012-04-20 09:42:08

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

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

或者

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我发现Perl自动执行此操作。 – stema 2012-04-20 09:49:39

+3

我试过了你的建议,但是没有改变,执行时间仍然是1m5.134s – xpapad 2012-04-20 10:06:38

+2

有几个挑剔:你需要在完成它之后释放文件描述符,或者调用'close'或者使用'File .open('filename'){| file | }',它确保文件关闭。另外,'/#{...}/'表示一个'Regexp'文字;调用'Regexp.new'是不必要的。 – 2012-04-20 11:44:02

perlretut chapter: Using regular expressions in Perl部分 - “搜索和替换”

(虽然正则表达式出现在一个循环中,Perl是足够聪明,只能编译一次。)

我不知道Ruby非常好,但我怀疑它在每个周期都会编译正则表达式。
(尝试从LaGrandMere的答案来验证它的代码)。

+0

我怀疑这一点。它有一个特殊的语法,所以它可能是在解析阶段构建的......这是循环之前的方式。 – remram 2015-04-12 18:48:22

一个可能的差异是执行回溯的数量。回溯时,Perl可能会更好地修剪搜索树(即注意模式的一部分不可能匹配)。它的正则表达式引擎是高度优化的。

首先,加入领先的« ^ »可以产生巨大的差异。如果模式不匹配从位置0开始,它也不会在起始位置1匹配!所以,不要试图以匹配位置1

按照同样的思路,« .*? »是不是限制性的,你可能会想,并与更多的限制模式取代它的每一个实例可以防止很多回溯。

你为什么不尝试:

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

(不知道是否是安全与« [^|] »,以取代第一« .*? »,所以我没有。)

(至少匹配单个字符串模式,(?:(?!PAT).)PAT[^CHAR]CHAR

使用/s可能可能加快速度,如果« . »允许匹配换行符,但我认为它很小。

使用的« \space »代替« [space] »到/x下匹配的空间可能会略快于红宝石。 (它们在最新版本的Perl中是相同的。)我使用后者,因为它更具可读性。

+0

@xpapad,调整了我的答案。 – ikegami 2012-04-20 19:21:36

红宝石:

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

变化match方法=~运营商。这是更快,因为:

(Ruby有基准我不知道你的文件的内容,所以我随意输入一些东西。)

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) 

输出报告:使用

 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) 

,中间的是=~。它只用不到其他人的三分之一。其他两种正在使用match方法。所以,在你的代码中使用=~

+0

我试过=〜而不是匹配,没有改变的表现。 – xpapad 2012-04-24 08:41:55

与其他形式的匹配相比,正则表达式匹配比较费时。由于您希望匹配行中间有一个很长的静态字符串,请尝试使用相对便宜的字符串操作过滤掉不包含该字符串的行。这应该会导致需要通过正则表达式解析的次数减少(当然,取决于您的输入是什么样子)。

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

由于我没有输入数据,因此我没有对这个特定版本进行基准测试。尽管过去我曾经成功地做过这样的事情,但是,尤其是在冗长的日志文件中,预过滤可以在不运行任何正则表达式的情况下消除绝大多数输入。

尝试使用(?>re)扩展名。见Ruby-Documentation详情,这里报价:

此构造[..]抑制回溯,它可以是一个 性能增强。例如,/a.*b.*a/的模式需要 与包含a 后跟b的一个字符串的字符串进行匹配时的指数时间,但没有结尾a。但是,可以通过使用嵌套正则表达式 /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