Perl正则表达式中的'o'修饰符是否仍然有用?

42

曾经在Perl正则表达式的末尾添加‘o’修饰符被认为很有益。但是当前的Perl文档甚至似乎没有列出它,至少在perlre的修饰符部分没有。

现在还有好处吗?

如果仅仅是为了向后兼容,它目前仍然可以接受。


正如J A Faucett和brian d foy所指出的,如果您找到正确的地方(其中之一不是perlre文档),仍然可以找到'o'修饰符的文档。 它在perlop页面中提到。 它还可以在perlreref页面中找到。

正如Alan M在接受的答案中指出的那样,更好的现代技术通常是使用qr//(引用的正则表达式)运算符。


你提到的页面确实描述了/o选项,但只在qr//和m//运算符的描述中提到。 - J. A. Faucett
1
@J A Faucett:嗯,我在那个页面上没有看到它,但是可以在perlop(http://perldoc.perl.org/perlop.html#Regexp-Quote-Like-Operators)页面中找到它的提及。它没有列在显眼的位置。我还发现它在perlreref(http://perldoc.perl.org/perlreref.html)中有列出。 - Jonathan Leffler
7个回答

38

/o已被弃用。确保正则表达式只编译一次的最简单方法是使用正则表达式对象,例如:

my $reg = qr/foo$bar/;

当变量$reg被初始化时,$bar的插值也会同时进行,并且缓存、编译后的正则表达式将在封闭作用域内继续使用。但有时您可能希望重新编译正则表达式,因为您想要它使用变量的新值。以下是Friedl在 The Book 中使用的示例:

sub CheckLogfileForToday()
{
  my $today = (qw<Sun Mon Tue Wed Thu Fri Sat>)[(localtime)[6]];

  my $today_regex = qr/^$today:/i; # compiles once per function call

  while (<LOGFILE>) {
    if ($_ =~ $today_regex) {
      ...
    }
  }
}

在这个函数的范围内,$today_regex的值保持不变。但是下一次调用函数时,正则表达式将会使用新的$today的值进行重新编译。如果他只是使用了:

if ($_ =~ m/^$today:/io)

使用对象形式,正则表达式将不会被更新。因此,您可以在不牺牲灵活性的情况下使用 /o 的效率。


我认为我的 $reg = qw/foo$bar/; 应该改为 $reg = qr/foo$bar/; - gpojd
你可以通过点击“编辑”链接来编辑你的答案,而且你应该这样做。 - J. A. Faucett
是的,我刚赶回来修复它,但乔纳森比我先完成了。谢谢你们所有人。 - Alan Moore
现在它已经被弃用,在某个 Perl 版本(?)开始。也许更新你的答案?但是不要包含“编辑:”,“更新:”或类似的内容——答案应该看起来像是今天写的。 - Peter Mortensen

19
/o修饰符在perlop文档中而不是perlre文档中,因为它是类似引用的修饰符而不是正则表达式修饰符。这对我来说一直很奇怪,但事实就是如此。自从Perl 5.20以来,/o现在列在perlre中只是为了说明您可能不应该使用它。
在Perl 5.6之前,即使变量没有改变,Perl也会重新编译正则表达式。您不再需要这样做。您可以使用/o将正则表达式编译一次,尽管变量后续更改,但正如其他答案所指出的那样,使用qr//更好。

7
在Perl 5版本20.0的文档http://perldoc.perl.org/perlre.html中,它指出:
Modifiers

Other Modifiers

…

o - pretend to optimize your code, but actually introduce bugs

这句话也许是一种幽默的说法,意思是它本应执行某种优化操作,但实现有问题。

因此,最好避免使用该选项。


2
技术背景是,在线程和非线程perl下,m//o的行为不同。在线程perl中,m/$foo/o不会使用新的$foo值重新编译正则表达式,但在没有线程的情况下会重新编译。这通常被认为是一个错误。直到5.005_02版本,您可以在编译时更改$foo,但不能在运行时更改,这更加一致(OPpRUNTIME)。但这从未得到修复,所以现在m/$foo/o在带有线程的perl中被认为是有缺陷的。在v5.26中,OPpRUNTIME标志被完全删除,而没有解释问题的背景。 - rurban
如果您将/o标志视为程序员承诺正则表达式不会更改,那么我认为这两种行为都不是错误。程序员对编译器说:我可以向您保证,这个插值$foo的正则表达式在首次编译后不会更改,因此请使用这些知识进行任何优化。编译器可以选择重新编译正则表达式或不重新编译(尽管显然不重新编译通常会更快)。话虽如此,/o的原始文档并没有真正用这些术语来描述它。 - Ed Avis

4

当正则表达式包含变量引用时,这是一种优化方法。它表明尽管正则表达式中有一个变量,但它并不会改变。这样可以进行其他情况下无法实现的优化。


优化还是漏洞?https://dev59.com/RsHqa4cB1Zd3GeqP0nzO#68454090 - Tim Potapov

2
以下是不同调用匹配的时间表。
$ perl -v | grep version
This is perl 5, version 20, subversion 1 (v5.20.1) built for x86_64-linux-gnu-thread-multi

$ perl const-in-re-once.pl | sort
0.200   =~ CONST
0.200   =~ m/$VAR/o
0.204   =~ m/literal-wo-vars/
0.252   =~ m,@{[ CONST ]},o
0.260   =~ $VAR
0.276   =~ m/$VAR/
0.336   =~ m,@{[ CONST ]},

我的代码:

#! /usr/bin/env perl

use strict;
use warnings;

use Time::HiRes qw/ tv_interval clock_gettime gettimeofday /;
use BSD::Resource qw/ getrusage RUSAGE_SELF /;

use constant RE =>
    qr{
        https?://
        (?:[^.]+-d-[^.]+\.)?
        (?:(?: (?:dev-)? nind[^.]* | mr02 )\.)?
        (?:(?:pda|m)\.)?
        (?:(?:news|haber)\.)
        (?:.+\.)?
        yandex\.
        .+
    }x;

use constant FINAL_RE => qr,^@{[ RE ]}(/|$),;

my $RE = RE;

use constant ITER_COUNT => 1e5;

use constant URL => 'http://news.trofimenkov.nerpa.yandex.ru/yandsearch?cl4url=www.forbes.ru%2Fnews%2F276745-visa-otklyuchila-rossiiskie-banki-v-krymu&lr=213&lang=ru';

timeit(
    '=~ m/literal-wo-vars/',
    ITER_COUNT,
    sub {
        for (my $i = 0; $i < ITER_COUNT; ++$i) {
            URL =~ m{
                ^https?://
                (?:[^.]+-d-[^.]+\.)?
                (?:(?: (?:dev-)? nind[^.]* | mr02 )\.)?
                (?:(?:pda|m)\.)?
                (?:(?:news|haber)\.)
                (?:.+\.)?
                yandex\.
                .+
                (/|$)
            }x
        }
    }
);

timeit(
    '=~ m/$VAR/',
    ITER_COUNT,
    sub {
        for (my $i = 0; $i < ITER_COUNT; ++$i) {
            URL =~ m,^$RE(/|$),
        }
    }
);

timeit(
    '=~ $VAR',
    ITER_COUNT,
    sub {
        my $r = qr,^$RE(/|$),o;
        for (my $i = 0; $i < ITER_COUNT; ++$i) {
            URL =~ $r
        }
    }
);

timeit(
    '=~ m/$VAR/o',
    ITER_COUNT,
    sub {
        for (my $i = 0; $i < ITER_COUNT; ++$i) {
            URL =~ m,^$RE(/|$),o
        }
    }
);

timeit(
    '=~ m,@{[ CONST ]},',
    ITER_COUNT,
    sub {
        for (my $i = 0; $i < ITER_COUNT; ++$i) {
            URL =~ m,^@{[ RE ]}(/|$),
        }
    }
);

timeit(
    '=~ m,@{[ CONST ]},o',
    ITER_COUNT,
    sub {
        for (my $i = 0; $i < ITER_COUNT; ++$i) {
            URL =~ m,^@{[ RE ]}(/|$),o
        }
    }
);

timeit(
    '=~ CONST',
    ITER_COUNT,
    sub {
        my $r = qr,^$RE(/|$),o;
        for (my $i = 0; $i < ITER_COUNT; ++$i) {
            URL =~ FINAL_RE
        }
    }
);

sub timeit {
    my ($name, $iters, $code) = @_;
    #my $t0 = [gettimeofday];
    my $t0 = (getrusage RUSAGE_SELF)[0];
    $code->();
    #my $el = tv_interval($t0);
    my $el = (getrusage RUSAGE_SELF)[0] - $t0;
    printf "%.3f\t%-17s\t%.9f\n", $el, $name, $el / $iters
}

1
欢迎来到Stack Overflow,请阅读[关于]页面。感谢您进行这些时间测试。前三个结果可能基本相同;后三个结果也可能“相同”,或者在时间上存在一些差异;最后一个结果似乎是独立的。您能否包含您在测试中使用的代码,以便我可以更准确地了解您所测量的内容?如果有数据文件,则不应将其包含在内,但确定文件的大小(行数和总字节数)会有所帮助。 - Jonathan Leffler

1

是和否

我使用以下脚本进行了简单比较:

perl -MBenchmark=cmpthese -E 'my @n = 1..10000; cmpthese(10000, {string => sub{"a1b" =~ /a\d+c/ for @n}, o_flag => sub{"a1b" =~ /a\d+c/o for @n}, qr => sub{my $qr = qr/a\d+c/; "a1b" =~ /$qr/ for @n } })'

这里是结果:

         Rate     qr string o_flag
qr      760/s     --   -72%   -73%
string 2703/s   256%     --    -5%
o_flag 2833/s   273%     5%     --

所以,很明显/o标志比使用qr要快得多
但是显然/o标志可能会引起错误Perl regex /o optimization or bug?

0

有一件令人困惑的事情是,在至少5.8.8版本中,它不允许使用ONCE块。

perl -le 'for (1..3){ print; m/${\(print( "between 1 and 2 only"), 3)}/o and print "matched" }'


发生了什么?哪里出错了?您能提供一些示例输出,最好附带评论吗? - Peter Mortensen
一个“once block”只会在第一次执行字符串内的插值打印。相反,它会在每次测试时执行。 - Never Sleep Again

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接