正则表达式引擎优化:`(?:。)*`真的比`。*`更昂贵?

时间:2014-03-28 15:13:52

标签: regex perl pcre

某些背景

我通常使用网站http://regex101.com来测试我的正则表达式,它在PCRE中提供了一个“调试器”功能,让您可以逐步了解正则表达式引擎正在做什么。

将随机字符串与.*匹配时,此调试器会告诉我引擎遵循常量3 steps

(?:.)*匹配时,会根据长度66 steps宣布一个数字,例如0123456789012345678901234567899

(?:.)*真的比.*贵吗?

似乎在后一种情况下,每次进入该组都被认为是一个新步骤,而在前一种情况下,.*被立即应用。

这是网站正在做的某种“改进”(试图避免显示无用的案例),还是与真正的内部正则表达式机制相匹配?如果是这样,那背后的想法是什么?

5 个答案:

答案 0 :(得分:4)

您没有指定哪个引擎。你提到了PCRE,但你也用Perl标记了。


regex101网站显示PCRE使用一个操作处理.*,但这并不意味着一个操作比等效(?:.)*生成的操作更快。只有基准测试才能证明,但由于开销较少,.*可能会略微加快。


在Perl中,它们编译为完全相同的正则表达式程序(如下所示),因此它们的表现相同。

>perl -Mre=debug -e"'0123456789012345678901234567899' =~ /.*/"
Compiling REx ".*"
Final program:
   1: STAR (3)
   2:   REG_ANY (0)
   3: END (0)
anchored(MBOL) implicit minlen 0
Matching REx ".*" against "0123456789012345678901234567899"
   0 <> <0123456789>         |  1:STAR(3)
                                  REG_ANY can match 31 times out of 2147483647...
  31 <901234567899> <>       |  3:  END(0)
Match successful!
Freeing REx: ".*"

>perl -Mre=debug -e"'0123456789012345678901234567899' =~ /(?:.)*/"
Compiling REx "(?:.)*"
Final program:
   1: STAR (3)
   2:   REG_ANY (0)
   3: END (0)
anchored(MBOL) implicit minlen 0
Matching REx "(?:.)*" against "0123456789012345678901234567899"
   0 <> <0123456789>         |  1:STAR(3)
                                  REG_ANY can match 31 times out of 2147483647...
  31 <901234567899> <>       |  3:  END(0)
Match successful!
Freeing REx: "(?:.)*"

在这两种情况下,都会扫描字符串中的字符而不是换行符。


请注意,无论有多少&#34;步骤&#34;被采取,这不能在恒定的时间内完成。 .不匹配换行符(没有/s),因此正则表达式引擎必须检查要匹配的每个字符,以查看它是否为换行符。< / p>

答案 1 :(得分:3)

您可以使用pcretest查看差异。 Here是一个很好的教程。

你的第一个例子显然需要几步,然后是第二个。在+的左侧,您可以看到模式中的位置,右侧是输入中的匹配位置。

带有调试修饰符/.*/CD

的str 0123456789012345678901234567899上的

1。)CD

pcretest1

2。)/(?:.)*/CD同样的str

pcretest2

那就是真正发生的事情。

答案 2 :(得分:2)

我编写了一个小的基准代码来测试这个场景。而且你的两个正则表达式在性能上几乎同时返回。所以不确定哪一个更好。

但是,我已将您的正则表达式(?:.)*更改为(.)*,这会大幅降低性能。我相信这是因为group capturing。这是代码:

use Benchmark qw( cmpthese );

cmpthese(-3, {    
    '.*'     => '"kasdaskdhas dhaskdh askdhqwioeuweakjsdhasjdk asjdk ask" =~ /.*/',
    '(?:.)*' => '"kasdaskdhas dhaskdh askdhqwioeuweakjsdhasjdk asjdk ask" =~ /(?:.)*/',
    '(.)*'   => '"kasdaskdhas dhaskdh askdhqwioeuweakjsdhasjdk asjdk ask" =~ /(.)*/',
});

输出:

            Rate   (.)* (?:.)*     .*
(.)*   2305921/s     --   -34%   -35%
(?:.)* 3499870/s    52%     --    -1%
.*     3524871/s    53%     1%     --

.*(?:.)*之间1%的差异是噪音而且毫无意义。

答案 3 :(得分:1)

该网站似乎无法解释 的步骤!

这必须是一个优化问题。问题&#34; (?:.)*确实比.*?&#34; 的成本更高,取决于正在使用的正则表达式引擎,并且该网站不太可能使用Perl&# 39; s正则表达式引擎,内置于perl编译器/解释器中。 使用的任何优化都选择忽略在现实世界中不太可能的(?:.)*之类的琐碎案例。

如果您需要正则表达式运行得更快,那么您应该使用Benchmark来比较不同的模式,或者Regexp::Optimizer,它们会尝试将您的模式重写为等效的模式,或{{3这将允许你看到幕后发生的事情。

但是请不要采取这些措施,直到你编写了一个功能和清晰的程序,该程序执行速度不够快,并证明瓶颈是正则表达式匹配。正则表达式引擎完全用C语言编写,你不太可能通过改变它使用的正则表达式来对代码的整体速度产生很大的影响。

答案 4 :(得分:0)

我不是这方面的专家,但据我所知,是的/(?:.)*//(.)*//.*/更贵。

根据backtracking上的Perl文档,

  

正则表达式匹配的基本特征涉及到   称为回溯的概念,目前使用(当需要时)   所有常规的非占有表达量词,即*,*? ,+,   +?,{n,m}和{n,m} ?.回溯通常在内部进行优化,但此处概述的一般原则是有效的。

基本上,.*在内部进行了优化,但我找不到一个说明如何的来源。

我还找到了另一个来源,blog作者Mastering Regular Expressions的帖子,Jeffrey Friedl。

  

顺便说一句,我想我应该提一下Perl的方法   有时会优化它如何处理正则表达式。有时   它可能实际上执行的测试比我描述的少。或者它   也许某些测试比其他测试更有效(例如,   / x * /在内部进行优化,使其效率更高   /(x) /或/(?: x) /)。有时Perl甚至可以决定正则表达式   永远不会匹配有问题的特定字符串,因此将绕过测试   共

如果其他人能够更详细地解释Perl所做的优化,那将是有用的!