为什么添加一个替代品使我的正则表达式慢600多倍?

时间:2015-03-23 18:07:46

标签: regex performance perl

我在测试一个简单的Perl脚本时注意到something weird,该脚本应该过滤掉以某些前缀开头的文件名。

基本上,我正在构建一个这样的正则表达式:

my $regex = join "|", map quotemeta, @prefixes;
$regex = qr/^($regex)/;   # anchor the regex and precompile it

这里,在我最初测试的场景中,@prefixes由32个字符的十六进制字符串组成。我发现一切都运行良好而顺畅,最多可达6,552个前缀 - 但是当我尝试6,553时,the script的执行时间超过了25(从1.8秒到51秒)!

我玩弄它,并构建了以下基准。我最初使用32个字符的十六进制字符串,就像在我的原始程序中一样,但发现如果我将字符串的长度减少到只有8个字符,则阈值移动得更高 - 实际上是16,383 - 而减速因子得到了显着的提升更高的是:16,383个替代品的正则表达式比只有16,382个的正常情况慢了650倍!

#!/usr/bin/perl
use strict;
use warnings;
use Benchmark qw(timethese cmpthese);

my $count = shift || 10;

our @items = map unpack("H8", pack "V", $_), 0..99999;

our $nA = 16382; our $reA = join "|", @items[1 .. $nA];
our $nB = 16383; our $reB = join "|", @items[1 .. $nB];

$_ = qr/^($_)/ for $reA, $reB;  # anchor and compile regex

my $results = timethese( $count, {
    $nA => q{ our (@items, $nA, $reA); $nA == grep /$reA/, @items or die; },
    $nB => q{ our (@items, $nB, $reB); $nB == grep /$reB/, @items or die; },
} );
cmpthese( $results );

以下是使用Perl(v5.18.2)在笔记本电脑上运行此基准测试的结果:

Benchmark: timing 10 iterations of 16382, 16383...
     16382:  2 wallclock secs ( 1.79 usr +  0.00 sys =  1.79 CPU) @  5.59/s (n=10)
     16383: 1163 wallclock secs (1157.28 usr +  2.70 sys = 1159.98 CPU) @  0.01/s (n=10)
      s/iter  16383  16382
16383    116     --  -100%
16382  0.179 64703%     --

注意64,703%的速度差异。

我原来的预感,基于6553≈2 16 / 10的数值巧合,这可能是Perl正则表达式引擎中的某种任意限制,或者可能是那里可能存在某种10字节结构的数组,其限制为64 kB,或者​​某种东西。但是,替代品的阈值数量取决于它们的长度这一事实使事情变得更加混乱。

(另一方面,它显然不仅仅是关于正则表达式的长度;具有6,553个32字节替代的原始正则表达式是2 + 6,553×33 = 216,251字节长,而具有16,383个8字节的正则表达式替代方案只有2 + 16,383×9 = 147,450字节长。)

在正则表达式匹配时间内造成这种奇怪跳跃的原因是什么?为什么会在特定点发生?

1 个答案:

答案 0 :(得分:17)

很长一段时间,perl的TRIE优化还没有应用于正则表达式的初始编译产生longjmp而不是jmp(我认为)指令(这取决于交替的数量和所涉及的字符串的总长度和什么在正则表达式中是(早先?)。

看到之间的区别:

perl -we'use re "debug"; qr/@{[join"|","a".."afhd"]}/'

perl -we'use re "debug"; qr/@{[join"|","a".."afhe"]}/'

您可以将更改分解为更小的块并单独预编译它们,例如(??{$rxa})|(??{$rxb})|(??{$rxc})