正则表达式中的回溯比预期的要快
Backtracking in regexp faster than expected
根据perlre,执行以下代码需要几秒钟:
$ time perl -E '$_="((()" . ("a") x 18; say "ok" if m{ \(([^()]+|\( [^()]* \))+\)}x;'
real 0m0.006s
user 0m0.000s
sys 0m0.005s
文档说:
Consider how the pattern above detects no-match on
((()aaaaaaaaaaaaaaaaaa
in several seconds, but that each extra
letter doubles this time.
如图所示,在我的笔记本电脑上只需要几分之一秒......
即使 运行 一百万 a
也能在半秒内完成:
$ time perl -E '$_="((()" . ("a") x 1000000; say "ok" if m{ \(([^()]+|\( [^()]* \))+\)}x;'
real 0m0.454s
user 0m0.446s
sys 0m0.008s
我在这里错过了什么?
您可以使用以下技巧来了解正则表达式引擎在做什么:
use re 'debug';
例如:
use strict;
use warnings;
use re 'debug';
my $str = "a" x 18;
$str =~ m{ \(([^()]+|\( [^()]* \))+\)}x;
然后这将打印正则表达式引擎实际在做什么:
Compiling REx " \(([^()]+|\( [^()]* \))+\)"
Final program:
1: EXACT <(> (3)
3: CURLYX[0] {1,32767} (40)
5: OPEN1 (7)
7: BRANCH (20)
8: PLUS (37)
9: ANYOF[^()][{above_bitmap_all}] (0)
20: BRANCH (FAIL)
21: EXACT <(> (23)
23: STAR (35)
24: ANYOF[^()][{above_bitmap_all}] (0)
35: EXACT <)> (37)
37: CLOSE1 (39)
39: WHILEM[1/3] (0)
40: NOTHING (41)
41: EXACT <)> (43)
43: END (0)
anchored "(" at 0 floating ")" at 2..2147483647 (checking floating) minlen 3
Matching REx " \(([^()]+|\( [^()]* \))+\)" against "aaaaaaaaaaaaaaaaaa"
Intuit: trying to determine minimum start position...
doing 'check' fbm scan, [2..18] gave -1
Did not find floating substr ")"...
Match rejected by optimizer
Freeing REx: " \(([^()]+|\( [^()]* \))+\)"
如果您重新添加括号,您会得到不同的结果 - 我需要大约 2000 步来处理正则表达式。每增加一个字母,它就会变长——大约 300 步。
所以我会说是的——灾难性的回溯正在发生,但您可能会发现处理器(和正则表达式引擎优化)意味着时间要短得多。
use strict;
use warnings;
use re 'debug';
my $str = "((()"."a" x 100_000;
$str =~ m{ \(([^()]+|\( [^()]* \))+\)}x;
运行时间相当长 - 但至少有一部分是因为将文本打印到屏幕上实际上相当 'expensive'。
我的测试表明("a" 的数量)
10 : 1221 lines of output (broadly correlated with regex steps)
11 : 1324 (+103)
12 : 1467 (+143)
13 : 1590 (+129)
14 : 1728 (+138)
15 : 1852 (+124)
20 : 2630 (approx +155/extra)
30 : 4536 (+190/extra)
40 : 6940 (+240/extra)
50 : 9846 (+290/extra)
100 - 31,846 (+440/extra letter)
所以看起来确实像指数行为一样 - 但在这两种情况下,处理时间仍然非常快,我将其归因于更快的 cpus(以及可能更好的正则表达式引擎优化)
Consider how the pattern above detects no-match on ((()aaaaaaaaaaaaaaaaaa
in several seconds.
这句话至少可以追溯到2001年4月,当时发布了perl 5.6.1。您可以在 search.cpan.org/~gsar/perl-5.6.1/pod/perlre.pod.
查看该版本的 perlre 手册页
这也许是在记录软件方面要吸取的教训。小心你写的东西,因为你写的文字将保持不变,尽管经过多年的改进和其他人的多次分叉。
根据perlre,执行以下代码需要几秒钟:
$ time perl -E '$_="((()" . ("a") x 18; say "ok" if m{ \(([^()]+|\( [^()]* \))+\)}x;'
real 0m0.006s
user 0m0.000s
sys 0m0.005s
文档说:
Consider how the pattern above detects no-match on
((()aaaaaaaaaaaaaaaaaa
in several seconds, but that each extra letter doubles this time.
如图所示,在我的笔记本电脑上只需要几分之一秒......
即使 运行 一百万 a
也能在半秒内完成:
$ time perl -E '$_="((()" . ("a") x 1000000; say "ok" if m{ \(([^()]+|\( [^()]* \))+\)}x;'
real 0m0.454s
user 0m0.446s
sys 0m0.008s
我在这里错过了什么?
您可以使用以下技巧来了解正则表达式引擎在做什么:
use re 'debug';
例如:
use strict;
use warnings;
use re 'debug';
my $str = "a" x 18;
$str =~ m{ \(([^()]+|\( [^()]* \))+\)}x;
然后这将打印正则表达式引擎实际在做什么:
Compiling REx " \(([^()]+|\( [^()]* \))+\)"
Final program:
1: EXACT <(> (3)
3: CURLYX[0] {1,32767} (40)
5: OPEN1 (7)
7: BRANCH (20)
8: PLUS (37)
9: ANYOF[^()][{above_bitmap_all}] (0)
20: BRANCH (FAIL)
21: EXACT <(> (23)
23: STAR (35)
24: ANYOF[^()][{above_bitmap_all}] (0)
35: EXACT <)> (37)
37: CLOSE1 (39)
39: WHILEM[1/3] (0)
40: NOTHING (41)
41: EXACT <)> (43)
43: END (0)
anchored "(" at 0 floating ")" at 2..2147483647 (checking floating) minlen 3
Matching REx " \(([^()]+|\( [^()]* \))+\)" against "aaaaaaaaaaaaaaaaaa"
Intuit: trying to determine minimum start position...
doing 'check' fbm scan, [2..18] gave -1
Did not find floating substr ")"...
Match rejected by optimizer
Freeing REx: " \(([^()]+|\( [^()]* \))+\)"
如果您重新添加括号,您会得到不同的结果 - 我需要大约 2000 步来处理正则表达式。每增加一个字母,它就会变长——大约 300 步。
所以我会说是的——灾难性的回溯正在发生,但您可能会发现处理器(和正则表达式引擎优化)意味着时间要短得多。
use strict;
use warnings;
use re 'debug';
my $str = "((()"."a" x 100_000;
$str =~ m{ \(([^()]+|\( [^()]* \))+\)}x;
运行时间相当长 - 但至少有一部分是因为将文本打印到屏幕上实际上相当 'expensive'。
我的测试表明("a" 的数量)
10 : 1221 lines of output (broadly correlated with regex steps)
11 : 1324 (+103)
12 : 1467 (+143)
13 : 1590 (+129)
14 : 1728 (+138)
15 : 1852 (+124)
20 : 2630 (approx +155/extra)
30 : 4536 (+190/extra)
40 : 6940 (+240/extra)
50 : 9846 (+290/extra)
100 - 31,846 (+440/extra letter)
所以看起来确实像指数行为一样 - 但在这两种情况下,处理时间仍然非常快,我将其归因于更快的 cpus(以及可能更好的正则表达式引擎优化)
Consider how the pattern above detects no-match on
((()aaaaaaaaaaaaaaaaaa
in several seconds.
这句话至少可以追溯到2001年4月,当时发布了perl 5.6.1。您可以在 search.cpan.org/~gsar/perl-5.6.1/pod/perlre.pod.
查看该版本的 perlre 手册页这也许是在记录软件方面要吸取的教训。小心你写的东西,因为你写的文字将保持不变,尽管经过多年的改进和其他人的多次分叉。