使用交替比使用正则表达式中的后续替换更快

时间:2016-04-05 08:06:01

标签: regex perl alternation

我有一个非常直截了当的问题。在我工作的地方,我看到很多正则表达式都来了。它们在Perl中用于替换和/或删除文本中的某些字符串,例如:

$string=~s/^.+\///;
$string=~s/\.shtml//;
$string=~s/^ph//;

我知道您无法连接第一个和最后一个替换,因为您可能只想在第一次替换后替换字符串开头的ph。但是,我会将第一个和第二个正则表达式放在一起进行交替:$string=~s/(^.+\/|\.shtml)//;因为我们正在处理数千个文件(+500,000),所以我想知道哪种方法最有效。

6 个答案:

答案 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)

perldoc perlre

中,如何在Perl中实现正则表达式更改
  

匹配此

     

我们可以通过交替匹配不同的字符串   元字符'|'。要匹配dogcat,我们会形成正则表达式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必须遍历两个表达式。

因此,第二种方法中的循环次数减少了。