Perl 显然是如何避免灾难性的回溯的?

How does Perl apparently avoid catastrophic backtracking?

提问人:shadowtalker 提问时间:9/27/2023 最后编辑:brian d foyshadowtalker 更新时间:9/29/2023 访问量:215

问:

我正在使用 Perl 5.34.1 和 Python 3.10.12。

以下脚本在 Python 中需要 16 秒:

import re
patt = re.compile(r"W(X|Y+)+Z")
print(patt.search("WYYYYYYYYYYYYYYYYYYYYYYYYYYYYA"))

但是在Perl中几乎不需要任何时间:

use v5.34;
my $patt = qr/W(X|Y+)+Z/;
print("WYYYYYYYYYYYYYYYYYYYYYYYYYYYYA" =~ $patt);

这是 https://medium.com/bigpanda-engineering/catastrophic-backtracking-the-dark-side-of-regular-expressions-80cab9c443f6 提供的灾难性回溯的一个例子。

我预计在这种情况下,任何回溯正则表达式引擎都会受到影响,但显然 Perl 正则表达式引擎不会。在此示例中,它如何避免灾难性的回溯?它是否真的在内部“灾难性地”回溯,但比 Python 引擎快几个数量级?它是否使用了一些快速路径优化来防止在完成完整的回溯序列之前尽早失败?

正则表达式 Perl

评论

0赞 Matt Timmermans 9/27/2023
我不确定 perl 中使用的精确算法,但它是通过记住部分结果来完成的。一旦确定字符串的某些后缀失败,就没有必要再次针对相同的后缀测试相同的子表达式。(X|Y+)*Z
0赞 shadowtalker 9/27/2023
@MattTimmermans如果你能详细说明一下,即使它不是一个完整的答案,那可能仍然值得回答,因为我认为这对其他对此感兴趣的人很有用。或者,如果您知道其他网站上存在的任何问题,也可以进行交叉链接。
1赞 Matt Timmermans 9/27/2023
fservant.github.io/papers/......

答:

8赞 ikegami 9/27/2023 #1

因为优化。它意识到需要一个,但它没有找到一个。Z

$ perl -Mre=debug -e'
    use v5.34;
    my $patt = qr/W(X|Y+)+Z/;
    print("WYYYYYYYYYYYYYYYYYYYYYYYYYYYYA" =~ $patt);
'
Compiling REx "W(X|Y+)+Z"
Final program:
   1: EXACT <W> (3)
   3: CURLYX<1:1>[0]{1,INFTY} (21)
   7:   OPEN1 (9)
   9:     BRANCH (buf:1/1) (13)
  11:       EXACT <X> (18)
  13:     BRANCH (buf:1/1) (FAIL)
  15:       PLUS (18)
  16:         EXACT <Y> (0)
  18:   CLOSE1 (20)
  20: WHILEM[1/1] (0)
  21: NOTHING (22)
  22: EXACT <Z> (24)
  24: END (0)
anchored "W" at 0..0 floating "Z" at 2..9223372036854775807 (checking floating) minlen 3
Matching REx "W(X|Y+)+Z" against "WYYYYYYYYYYYYYYYYYYYYYYYYYYYYA"
Intuit: trying to determine minimum start position...
  doing 'check' fbm scan, [2..30] gave -1
  Did not find floating substr "Z"...
Match rejected by optimizer
Freeing REx: "W(X|Y+)+Z"

也就是说,使用会绕过该优化。qr/W(X|Y+)+(Z|!)/

$ perl -Mre=debug -e'
    use v5.34;
    my $patt = qr/W(X|Y+)+Z/;
    print("WYYYYYYYYYYYYYYYYYYYYYYYYYYYYA" =~ $patt);
' 2>&1 | wc -l
22

$ perl -Mre=debug -e'
    use v5.34;
    my $patt = qr/W(X|Y+)+(Z|!)/;
    print("WYYYYYYYYYYYYYYYYYYYYYYYYYYYYA" =~ $patt);
' 2>&1 | wc -l
2971

尽管如此,在我的机器上,差异还不到 1 毫秒。

$ time perl -e'
    use v5.34;
    my $patt = qr/W(X|Y+)+Z/;
    print("WYYYYYYYYYYYYYYYYYYYYYYYYYYYYA" =~ $patt);
' 2>&1 | wc -l
0

real    0m0.002s
user    0m0.002s
sys     0m0.000s

$ time perl -e'
    use v5.34;
    my $patt = qr/W(X|Y+)+(Z|!)/;
    print("WYYYYYYYYYYYYYYYYYYYYYYYYYYYYA" =~ $patt);
' 2>&1 | wc -l
0

real    0m0.002s
user    0m0.002s
sys     0m0.000s

显然,还有其他优化。 此版本包含以下消息:-Mre=debug

WHILEM: Detected a super-linear match, switching on caching...

你可以试着看看Yves “demerphq” Orton是否就此做过任何演讲。

2赞 Dave Mitchell 9/29/2023 #2

在其他优化中,perl 的正则表达式引擎有一个叫做超线性缓存的东西。经过一定次数的迭代后,引擎确定匹配已变为“超线性”,并分配一个缓存,该缓存是与字符串长度相似的字节数组。它使用此缓存中的位来标记字符串中的某些匹配位置,这些位置在模式中的某个点失败,如果达到类似位置,它们将始终再次失败。因此,跳过了类似案例的无休止的重试。 是否使用此技术取决于模式的细节。