Ilm*_*nen 22 regex perl performance
在测试一个简单的Perl脚本时,我发现了一些奇怪的东西,它应该过滤掉以某些前缀开头的文件名.
基本上,我正在构建一个这样的正则表达式:
my $regex = join "|", map quotemeta, @prefixes;
$regex = qr/^($regex)/; # anchor the regex and precompile it
Run Code Online (Sandbox Code Playgroud)
这里,在我最初测试的场景中,@prefixes
由32个字符的十六进制字符串组成.我发现一切都运行良好而顺畅,最多可达6,552个前缀 - 但是一旦我尝试了6,553,脚本的执行时间就会超过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 );
Run Code Online (Sandbox Code Playgroud)
以下是使用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% --
Run Code Online (Sandbox Code Playgroud)
注意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字节长.)
在正则表达式匹配时间内导致这种奇怪跳跃的原因是什么?为什么会在特定点发生?
yst*_*sth 17
很长一段时间,perl的TRIE优化还没有应用于正则表达式的初始编译产生longjmp而不是jmp(我认为)指令(这取决于交替的数量和所涉及的字符串的总长度以及其他是什么(早期?)在正则表达式中).
看看之间的区别:
perl -we'use re "debug"; qr/@{[join"|","a".."afhd"]}/'
Run Code Online (Sandbox Code Playgroud)
和
perl -we'use re "debug"; qr/@{[join"|","a".."afhe"]}/'
Run Code Online (Sandbox Code Playgroud)
您可以将交替分解为较小的块并单独预编译,例如(??{$rxa})|(??{$rxb})|(??{$rxc})
.