Perl正则表达式意外的性能下降

3

Perl v5.28.1

基准测试:

use common::sense;
use Benchmark qw(:all);

my $UPPER = 10_000_000;
my $str = 'foo bar baz';

cmpthese(10, {
        'empty for-loop' => sub {
                        for my $i (1..$UPPER) {}
                },
        'regex match' => sub {
                        for my $i (1..$UPPER) {
                                $str =~ /foo/;
                        }
                },
        'regex match (single compile)' => sub {
                        my $re = qr/foo/;
                        for my $i (1..$UPPER) {
                                $str =~ $re;
                        }
                },
        'regex match (anchor)' => sub {
                        for my $i (1..$UPPER) {
                                $str =~ /^foo/;
                        }
                },
        'regex match (anchor) (single compile)' => sub {
                        my $re = qr/^foo/;
                        for my $i (1..$UPPER) {
                                $str =~ $re;
                        }
                },
});

结果如下:
                                      s/iter regex match (anchor) (single compile) regex match (single compile) regex match (anchor) regex match empty for-loop
regex match (anchor) (single compile)   3.83                                    --                         -21%                 -60%        -84%           -97%
regex match (single compile)            3.04                                   26%                           --                 -50%        -80%           -96%
regex match (anchor)                    1.53                                  151%                          99%                   --        -61%           -92%
regex match                            0.601                                  537%                         405%                 154%          --           -81%
empty for-loop                         0.117                                 3170%                        2496%                1205%        414%             --

因为foo恰好出现在字符串开头,所以我期望在正则表达式中添加明确的锚点(^)不会对性能造成影响,而不是让性能减半!
此外,我读到过这样的内容:即使包含在循环内部,Perl也足够智能,不会重新编译包含固定字符串的表达式。但是,为什么试图手动/显式地将表达式预编译为变量$re会导致如此大的性能损失?!
我将搜索子字符串“foo”更改为“asdf”(它不存在于$str中),并且锚定确实可以让引擎更早地退出搜索。但是将表达式分配给变量仍然会对性能造成巨大的影响 - 远远超出我的预期!
                                         Rate regex match (single compile) regex match (anchor) (single compile) regex match regex match (anchor) empty for-loop
regex match (single compile)          0.401/s                           --                                  -10%        -79%                 -83%           -96%
regex match (anchor) (single compile) 0.447/s                          11%                                    --        -76%                 -81%           -95%
regex match                            1.88/s                         369%                                  321%          --                 -19%           -79%
regex match (anchor)                   2.33/s                         481%                                  421%         24%                   --           -75%
empty for-loop                         9.17/s                        2185%                                 1951%        387%                 294%             --

总结两个问题:
- 为什么字符串起始锚定符会减半性能?
- 编译表达式(qr//)到变量中为什么比在行内使用同一表达式慢80%?


你的列表为什么显示不同的行?眼睛看到的是代码,而不是数字,这是参考。我开始进行相关性分析,但最终放弃了。 - user557597
不确定您所说的“不同行”是什么意思? 模块“Benchmark”按照速度从慢到快排序,以进行表格比较。当然,不同的基准条件将导致排序不同的列表,与我在代码中定义匿名子程序的顺序无关。 - robut
“我也不确定你所说的不同行是什么意思”,但是你的第二个输出结果并不是来自所示程序,也许这就是他们的意思?更重要的是,你说“慢了80%”,但是我在基准测试输出中看到了五倍的因素(我也得到了三倍的因素)……我是否漏掉或误读了什么? - zdim
@zdim 是的,第二个不是程序生成的。引用:“我将搜索子字符串“foo”更改为“asdf”(该字符串未出现在$str中)[...]”。如果您滚动第二个输出,您还会看到Benchmark已经量化了第1行(“正则表达式匹配(单个编译)”)相对于其对应项(“正则表达式匹配”)的性能下降了79%。对于锚定的两个也是如此(第2行的下降率为81%,请比较倒数第二列)。 - robut
在第一个输出中,“_regex match (single compile)_”的速度为3.04秒/迭代,而“_regex match_”的速度为0.601秒/迭代;在第二个输出中,速率分别为0.4/s和1.88/s。这是大约5倍的因素...? - zdim
1个回答

5

在添加锚点时,会阻止特定的正则表达式优化。这个问题已经在5.30.0中得以解决。

目前使用qr//对象会有一些轻微的惩罚,因为内部要复制部分正则表达式结构(与每个正则表达式对象都有自己的捕获索引集相关)。目前还没有找到一个好的解决办法。


嘿,这是很棒的见解,谢谢!我切换到了v5.30.0,确实使用^锚点的正则表达式比没有锚点的更快。我的想法很少是,“嗯,一定是Perl的bug”! - robut
2
你能详细说明其中任何一点,或提供链接吗?"slight penalty"对于qr对象来说并不能解释OP所显示的三倍差距(我在我的测试中也证实了这一点)。此外,什么样的优化有助于锚定模式匹配,使其始终位于字符串开头(这是测试的内容,而不是失败)? - zdim
这实际上是一个因子为5的问题,他们显示出了3的因子,使用5.16.3时我得到了3的因子。而使用5.29.2接近5倍。 - zdim
1
简单的模式 /^foo/ 已经被大量优化,甚至不会调用正则表达式引擎。对于需要实时运行的复杂正则表达式来说,每次克隆正则表达式的开销变得微不足道。 - Dave Mitchell
希望未来的Perl版本能够改进这一点!虽然公平地说...我想在这种微不足道的情况下,“改进”这种差异可能等同于过早优化...而且我还没有测试性能如何与更复杂的表达式相比,所以这个问题在“真实”的代码中可能是无关紧要的。 - robut

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接