我注意到我的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
我想我正在做一些非常愚蠢的事情,有什么建议吗?
谢谢
答案 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()
我没有对此特定版本进行基准测试,因为我没有输入数据。我在过去做过这样的事情已经取得了成功,特别是对于冗长的日志文件,预过滤可以消除绝大多数输入而不运行任何正则表达式。