【问题标题】:Why is using a regex pre-compiled with qr slower than using a constant regex?为什么使用 qr 预编译的正则表达式比使用常量正则表达式慢?
【发布时间】:2017-03-24 22:08:02
【问题描述】:

我刚刚看到this question 关于优化 Perl 中的特定正则表达式。我想知道我的机器可以匹配多少次,所以我尝试了以下简单的基准测试:

  • 案例 1 - 使用用 qr 预编译的正则表达式
  • 案例 2 - 普通 /regex/ 匹配
use 5.014;
use warnings;

use Benchmark qw(:all);

my $str = "SDZ";
my $qr = qr/S?T?K?P?W?H?R?A?O?\*?E?U?F?R?P?B?L?G?T?S?D?Z?/;

say "match [$&]" if( $str =~ $qr );

my $res = timethese(-10, {
    stdrx => sub { $str =~ /S?T?K?P?W?H?R?A?O?\*?E?U?F?R?P?B?L?G?T?S?D?Z?/ },
    qr_rx => sub { $str =~ $qr },
});

cmpthese $res;

令我惊讶的是,它给出了以下结果:

match [SDZ]
Benchmark: running qr_rx, stdrx for at least 10 CPU seconds...
     qr_rx: 10 wallclock secs ( 9.99 usr +  0.01 sys = 10.00 CPU) @ 1089794.90/s (n=10897949)
     stdrx: 11 wallclock secs (10.58 usr +  0.04 sys = 10.62 CPU) @ 1651340.11/s (n=17537232)
           Rate qr_rx stdrx
qr_rx 1089795/s    --  -34%
stdrx 1651340/s   52%    --

即普通的$str =~ /regex/ 比使用$str =~ qr 快大约50%。我期待相反的结果。

我做错了吗?为什么我会得到这个结果?

编辑:

只要downloaded引用的书,我还有很多要学的:)。但是,引用的书也说:

如果正则表达式没有变量插值,Perl 知道正则表达式不能从使用变为使用,因此在正则表达式编译一次后,编译后的形式被保存(“缓存”)以供再次执行到达相同代码时使用.正则表达式只检查和编译一次,无论它在程序执行期间使用的频率如何。

所以,在上面两个正则表达式都是 literal 没有变量插值。所以,“预编译”的正则表达式应该和普通的一样快。在示例中,它慢了 50%。

Ikegami 解释了为什么$str =~ $qr 更慢。 (老实说,“较慢”不是正确的术语,因为我们谈论的是几微秒...... :))

但是 perl 文档说:

模式预编译成内部表示 qr() 的时刻避免了每次重新编译模式的需要 尝试匹配 /$pat/。

从普通 perl 用户(“不是一些高级 perl 修士”)的角度来看,这意味着:预编译您的模式 - 会更快,但事实是 - 它有帮助仅当正则表达式包含一些“非静态”部分时...

老实说,我仍然没有完全理解这一点 - 但得到了一本书并准备学习。 :) 文档中可能多说一句——可以帮助初学者在开始学习时不要误解qr

谢谢大家!

【问题讨论】:

  • 您为什么期望qr 更快?另外,如果您从程序中删除 $&,会发生什么变化吗?
  • @melpomene 因为文档说:在 qr() 时刻将模式预编译为内部表示避免了每次尝试匹配 /$pat/ 时都需要重新编译模式. 我的理解是:编译需要更多的处理,所以它必须更慢。 (看起来那是无效的)。 (删除 $& 没有任何变化)
  • 但是您的代码中没有/$pat/。您所有的正则表达式都是静态的,没有变量插值。 qr 在这里给您的唯一内容是间接级别,因为在匹配点它必须获取 $qr 的内容并测试它是正则表达式对象还是字符串,而 =~ /.../ 将正则表达式存储为部分比赛操作。
  • 您好。我启动了您引用的线程。我在qr 中使用命名正则表达式引用的原因是因为该正则表达式在程序中的多个位置使用。它有助于避免拼写错误以及与其他一些外观相似的正则表达式混淆,以查看 $valid_steno 而不是另一个冗长的正则表达式,抛开性能问题。
  • Re "在示例中,它慢了 50%。",不完全是。它慢了一个恒定的量。

标签: perl


【解决方案1】:

如果不进行插值,正则表达式模式将在编译时编译。 qr// 运算符中的正则表达式和 stdrx 中的匹配运算符中的正则表达式都不是插值,因此两者都是在编译时编译的。

qr_rx 测试中花费的额外 30μs 用于“编译”第三个正则表达式:qr_rx 中匹配运算符中的那个。不要忘记$_ =~ $re$_ =~ m/$re/ 的缩写。现在,当整个模式由一个插值的预编译正则表达式组成时,实际上不会发生编译,因为这种情况是经过特殊处理的,但显然仍然需要一些时间来诱使匹配操作使用预编译的正则表达式。 (也许它需要克隆它?)

【讨论】:

  • 感谢您的洞察力。但是这样不是很奇怪吗?由此看来,如果不再次编译正则表达式,就不可能在匹配操作中使用预编译的正则表达式(在运行时)。这意味着使用已编译的正则表达式永远不会更快?
  • 像往常一样 - 很好,详细和有用的答案。无论如何,qr 应该有更多/更好的记录。我也问过我的同事,他们和我一样理解文档,:)
  • @HåkonHægland 1) 我没有费心单步执行代码,但我猜这不是“完整”的重新编译;每次循环迭代仍然有一个 regcomp 操作码,但它只做一些琐碎的工作,比如克隆预编译的正则表达式。 2) qr 不能比常量正则表达式快。当您将变量插入模式时,它可以更快(尽管并非总是如此;循环内的/$foo/ 仅编译一次并在$foo 永不更改时缓存,但每次迭代都有一个字符串比较; 如果这比 qr 的开销快,那么 qr 仍然会失败。(续)
  • @HåkonHægland 请参阅Mastering Regular Expressions 的第 350 页上的“准备正则表达式的内部机制”(但请忽略有关 /o 修饰符的部分,该修饰符已过时)。您也可以自己对此进行基准测试:比较 'foo' =~ /$foo/$qr = qr/$foo/; 'foo' =~ $qr,当 $foo 的长度为 100 与 100 万个字符时。
  • 你所做的只是添加一个间接层。 @res 中的模式是否被编译在这里会有所不同:for (...) { for my $re (@res) { ... /$re/ ... } }
猜你喜欢
  • 2018-05-08
  • 1970-01-01
  • 2015-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
  • 1970-01-01
相关资源
最近更新 更多