我有一个非常直截了当的问题。在我工作的地方,我看到很多正则表达式都来了。它们在Perl中用于替换和/或删除文本中的某些字符串,例如:
$string=~s/^.+\///;
$string=~s/\.shtml//;
$string=~s/^ph//;
我知道您无法连接第一个和最后一个替换,因为您可能只想在第一次替换后替换字符串开头的ph
。但是,我会将第一个和第二个正则表达式放在一起进行交替:$string=~s/(^.+\/|\.shtml)//;
因为我们正在处理数千个文件(+500,000),所以我想知道哪种方法最有效。
答案 0 :(得分:10)
此:
$string=~s/^.+\///;
$string=~s/\.shtml//;
替换文字.shtml
和所有内容,包括最后一个斜杠。
此:
$string=~s/(^.+\/|\.shtml)//;
替换 文本.shtml
或所有内容,包括最后一个斜杠。
这是组合正则表达式的一个问题:单个复杂正则表达式比几个简单的正则表达式更难编写,更难理解,更难调试。
即使您的表达式等同于,使用其中一个可能也不会对您的程序速度产生重大影响。像s///
这样的内存中操作明显快于文件I / O,并且您已经表明您正在执行大量文件I / O.
您应该使用类似Devel::NYTProf的内容来分析您的应用程序,看看这些特定的替换是否实际上是一个瓶颈(我怀疑它们是否存在)。不要浪费你的时间来优化已经很快的事情。
请注意,您要比较苹果和橙子,但如果您仍然对性能感到好奇,可以看看perl如何使用re
pragma评估特定的正则表达式:
$ perl -Mre=debug -e'$_ = "foobar"; s/^.+\///; s/\.shtml//;'
...
Guessing start of match in sv for REx "^.+/" against "foobar"
Did not find floating substr "/"...
Match rejected by optimizer
Guessing start of match in sv for REx "\.shtml" against "foobar"
Did not find anchored substr ".shtml"...
Match rejected by optimizer
Freeing REx: "^.+/"
Freeing REx: "\.shtml"
正则表达式引擎有一个优化器。优化器搜索必须出现在目标字符串中的子字符串;如果找不到这些子字符串,则匹配会立即失败,而不会检查正则表达式的其他部分。
使用/^.+\//
,优化器知道$string
必须包含至少一个斜杠才能匹配;当它找不到斜杠时,它立即拒绝匹配而不调用完整的正则表达式引擎。使用/\.shtml/
进行类似的优化。
这是perl对组合正则表达式所做的事情:
$ perl -Mre=debug -e'$_ = "foobar"; s/(?:^.+\/|\.shtml)//;'
...
Matching REx "(?:^.+/|\.shtml)" against "foobar"
0 <> <foobar> | 1:BRANCH(7)
0 <> <foobar> | 2: BOL(3)
0 <> <foobar> | 3: PLUS(5)
REG_ANY can match 6 times out of 2147483647...
failed...
0 <> <foobar> | 7:BRANCH(11)
0 <> <foobar> | 8: EXACT <.shtml>(12)
failed...
BRANCH failed...
1 <f> <oobar> | 1:BRANCH(7)
1 <f> <oobar> | 2: BOL(3)
failed...
1 <f> <oobar> | 7:BRANCH(11)
1 <f> <oobar> | 8: EXACT <.shtml>(12)
failed...
BRANCH failed...
2 <fo> <obar> | 1:BRANCH(7)
2 <fo> <obar> | 2: BOL(3)
failed...
2 <fo> <obar> | 7:BRANCH(11)
2 <fo> <obar> | 8: EXACT <.shtml>(12)
failed...
BRANCH failed...
3 <foo> <bar> | 1:BRANCH(7)
3 <foo> <bar> | 2: BOL(3)
failed...
3 <foo> <bar> | 7:BRANCH(11)
3 <foo> <bar> | 8: EXACT <.shtml>(12)
failed...
BRANCH failed...
4 <foob> <ar> | 1:BRANCH(7)
4 <foob> <ar> | 2: BOL(3)
failed...
4 <foob> <ar> | 7:BRANCH(11)
4 <foob> <ar> | 8: EXACT <.shtml>(12)
failed...
BRANCH failed...
5 <fooba> <r> | 1:BRANCH(7)
5 <fooba> <r> | 2: BOL(3)
failed...
5 <fooba> <r> | 7:BRANCH(11)
5 <fooba> <r> | 8: EXACT <.shtml>(12)
failed...
BRANCH failed...
Match failed
Freeing REx: "(?:^.+/|\.shtml)"
注意输出多长时间。由于交替,优化器不会启动并执行完整的正则表达式引擎。在最坏的情况下(没有匹配),交替的每个部分都针对字符串中的每个字符进行测试。这不是很有效。
所以,交替变慢了,对吧?不,因为......
同样,我们正在比较苹果和橘子,但是:
$string = 'a/really_long_string';
组合的正则表达式实际上可能更快,因为使用s/\.shtml//
时,优化器必须在拒绝匹配之前扫描大部分字符串,而组合的正则表达式会快速匹配。
你可以benchmark这很有趣,但是因为你要比较不同的事情,所以它基本上没有意义。
答案 1 :(得分:3)
匹配此
我们可以通过交替匹配不同的字符串 元字符
'|'
。要匹配dog
或cat
,我们会形成正则表达式dog|cat
。 和以前一样,Perl会尽可能地匹配正则表达式 指向字符串。在每个角色位置,Perl将首先尝试 匹配第一个替代方案dog
。如果dog
不匹配,Perl会 然后尝试下一个替代方案cat
。如果cat
也不匹配,那么 匹配失败,Perl移动到字符串中的下一个位置。 一些例子:"cats and dogs" =~ /cat|dog|bird/; # matches "cat" "cats and dogs" =~ /dog|cat|bird/; # matches "cat"
尽管
dog
是第二个正则表达式中的第一个替代方案,但cat
能够匹配 在字符串的早期。"cats" =~ /c|ca|cat|cats/; # matches "c" "cats" =~ /cats|cat|ca|c/; # matches "cats"
这里,所有替代方案都匹配第一个字符串位置,所以 第一种选择是匹配的。如果有一些替代方案 是其他人的截断,最长的是给他们 有机会匹配。
"cab" =~ /a|b|c/ # matches "c" # /a|b|c/ == /[abc]/
最后一个例子指出 字符类就像是字符的替换。在给定的 字符位置,允许正则表达式匹配的第一个替代方案 成功将是匹配的。
所以这应该解释你在正则表达式中使用替换时支付的价格。
将简单的正则表达式放在一起时,您不需要付出这么大的代价。 SO中的另一个相关question对此进行了很好的解释。当直接搜索常量字符串或问题中的一组字符时,可以进行优化,不需要回溯,这意味着代码可能更快。
在定义正则表达式替换时,只需选择 good 顺序(将最常见的结果放在第一位)就会影响性能。选择两个选项或二十个选项是不一样的。与往常一样,过早优化是所有邪恶的根源如果存在问题或者您需要改进,您应该指导您的代码(Devel::NYTProf)。但作为一般规则,交替应保持在最低限度,并尽可能避免,因为:
希望这个答案更贴近您的期望。
答案 2 :(得分:1)
首先,测量真实数据的各种选项,因为没有多少理论可以击败实验(如果可以做到的话)。 CPAN上有许多计时模块可以帮助您。
其次,如果您决定优化正则表达式,请不要手动将它们压成一个巨大的怪物,尝试用代码组装“主”正则表达式。否则,没有人能够破译代码。
答案 3 :(得分:1)
如果你有三个正常运作良好的正则表达式,那么组合它们没有任何好处。重写它们不仅为错误打开了大门,而且程序员和引擎都难以阅读正则表达式。
This page建议改为:
while (<FILE>) {
next if (/^(?:select|update|drop|insert|alter)\b/);
...
}
您应该使用:
while (<FILE>) {
next if (/^select/);
next if (/^update/);
...
}
您可以使用正则表达式对象,这将确保您的正则表达式不会在循环中重新编译:
my $query = qr/foo$bar/; #compiles here
@matches = ( );
...
while (<FILE>) {
push @matches, $_ if /$query/i;
}
您也可以优化.+
。它将占用整个文件,然后必须逐个字符地回溯,直到它找到/
以便它可以匹配。如果每个文件只有一个/
,请尝试使用否定的字符类:[^/]
(转义:[^\/]
)。您希望在文件中找到/
的位置?知道这将使你的正则表达式变得更快。
如果您遇到性能问题(目前有3个正则表达式),它可能是您程序的不同部分。虽然计算机的处理速度呈指数级增长,但读写速度却几乎没有增长。
Perl使用NFA,它比sed的DFA引擎更慢但功能更强大。 NFA回溯(特别是改动)并且具有最坏情况的指数运行时间。 DFA具有线性执行时间。您的模式不需要NFA引擎,因此您可以非常轻松地在DFA引擎(如sed)中使用正则表达式。
根据here sed可以以每秒处理 82.1百万字符的速度进行搜索和替换(注意此测试是写入/dev/null
,所以硬盘写入速度并不是真正的因素。
答案 4 :(得分:0)
可能有点偏离主题,但如果实际替换很少,相对于comapares的数量(10%-20%?),您可能会先使用索引匹配获得一些速度
$string=~s/\.shtml//
if index($string, ".shtml");
答案 5 :(得分:-2)
第二种方法最好将第一和第二个正则表达式与交替放在一起。 因为在该方法中,perl会遍历一次,并检查两个表达式。
如果你使用第一种方法,其中perl必须遍历两个表达式。
因此,第二种方法中的循环次数减少了。