正则表达式 - 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
我想我做的事情非常愚蠢的,有什么建议?
谢谢
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
从perlretut chapter: Using regular expressions in Perl部分 - “搜索和替换”
(虽然正则表达式出现在一个循环中,Perl是足够聪明,只能编译一次。)
我不知道Ruby非常好,但我怀疑它在每个周期都会编译正则表达式。
(尝试从LaGrandMere的答案来验证它的代码)。
我怀疑这一点。它有一个特殊的语法,所以它可能是在解析阶段构建的......这是循环之前的方式。 – 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中是相同的。)我使用后者,因为它更具可读性。
@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
方法。所以,在你的代码中使用=~
。
我试过=〜而不是匹配,没有改变的表现。 – 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
您是否尝试过'如果行=〜/(.*)\ |?。?。?。*发送请求* TID =(*),/'?这也适用于Ruby,如果它具有不同的性能特征,我会很好奇。 – 2012-04-20 09:42:08