正则表达式 - Ruby vs Perl

时间:2012-04-20 09:15:56

标签: ruby regex perl

我注意到我的Ruby(1.9)脚本有一些极端的延迟,经过一些挖掘,它归结为正则表达式匹配。我在Perl和Ruby中使用以下测试脚本:

的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:

xenofon@cpm:~/bin/local/project$ time ./try.pl input >/dev/null

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

红宝石:

xenofon@cpm:~/bin/local/project$ time ./try.rb input >/dev/null

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

我想我正在做一些非常愚蠢的事情,有什么建议吗?

谢谢

6 个答案:

答案 0 :(得分:7)

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

答案 1 :(得分:5)

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

  

(即使正则表达式出现在循环中,Perl也足够聪明,只能编译一次。)

我不太了解Ruby,但我怀疑它确实在每个周期编译正则表达式 (尝试使用LaGrandMere的答案来验证它)。

答案 2 :(得分:5)

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

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

同样,“.*?”并不像你想象的那样限制,用一个更有限的模式替换它的每一个实例都可以防止大量的回溯。

你为什么不试试:

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

(不确定用{.*?»替换第一个«[^|]»是否安全,所以我没有。)

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

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

使用«\space»而不是«[space]»来匹配/x下的空格可能会稍微快一些。 (它们在最新版本的Perl中是相同的。)我使用了后者,因为它更具可读性。

答案 3 :(得分:2)

尝试使用(?>re)扩展程序。有关详情,请参阅Ruby-Documentation,此处为引用:

  

这个构造[...]抑制回溯,可以是a   性能提升。例如,模式/a.*b.*a/需要   与包含a的字符串匹配时的指数时间   后跟一些b s,但没有尾随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

答案 4 :(得分:1)

红宝石:

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

match方法更改为=~运算符。它更快,因为:

(Ruby有Benchmark。我不知道你的文件内容,所以我随机输入了一些内容)

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)

中间人正在使用=~。它只需不到其他人的1/3。其他两个使用match方法。因此,请在代码中使用=~

答案 5 :(得分:1)

与其他形式的匹配相比,正则表达式匹配非常耗时。由于您期望匹配行中间有一个长的静态字符串,因此请尝试使用相对便宜的字符串操作过滤掉不包含该字符串的行。这应该导致需要通过正则表达式解析(当然,取决于您的输入的样子)。

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

我没有对此特定版本进行基准测试,因为我没有输入数据。我在过去做过这样的事情已经取得了成功,特别是对于冗长的日志文件,预过滤可以消除绝大多数输入而不运行任何正则表达式。