RegEx字词表现:\ w与[a-zA-Z0-9_]

时间:2019-04-16 01:40:00

标签: c# php regex perl pcre

我想知道\w通过的字符列表,是[a-zA-Z0-9_]还是它可能涵盖的更多字符?

我问的是这个问题,因为基于this\d[0-9]不同,并且是less efficient

  

\w[a-zA-Z0-9_]:在大规模生产中哪个更快?

3 个答案:

答案 0 :(得分:5)

[此答案是Perl特定的。其中的信息可能不适用于PCRE或其他已标记语言的引擎。]

/\w/aa(实际上等效于/[a-zA-Z0-9_]/)通常更快,但并非总是如此。就是说,差异是如此之小(每次检查少于1纳秒),所以不必担心。要将其放在上下文中,调用子程序或启动正则表达式引擎需要花费很长的时间。

下面将详细介绍这一点。


首先,默认情况下\w[a-zA-Z0-9_]是不同的。 \w与每个匹配 字母,数字,标记和连接器标点Unicode代码点。其中有119,821个! [1] 确定最快的非等效代码是没有道理的。

不过,将\w/aa结合使用可确保\w仅与[a-zA-Z0-9_]相匹配。这就是我们将用于基准测试的内容。 (实际上,我们会同时使用。)

(请注意,每个测试执行1000万次检查,因此10.0 / s的速率实际上意味着每秒1000万次检查。)


ASCII-only positive match
               Rate [a-zA-Z0-9_]      (?u:\w)     (?aa:\w)
[a-zA-Z0-9_] 39.1/s           --         -26%         -36%
(?u:\w)      52.9/s          35%           --         -13%
(?aa:\w)     60.9/s          56%          15%           --

当找到ASCII字符匹配项时,纯ASCII \w和Unicode \w都击败了显式类。

/\w/aa是(1 / 39.1-1 / 60.9)/ 10,000,000 = 0.000,000,000,916 s在我的计算机上更快


ASCII-only negative match
               Rate      (?u:\w)     (?aa:\w) [a-zA-Z0-9_]
(?u:\w)      27.2/s           --          -0%         -12%
(?aa:\w)     27.2/s           0%           --         -12%
[a-zA-Z0-9_] 31.1/s          14%          14%           --

如果找不到ASCII字符匹配项,则显式类将击败仅使用ASCII的\w

/[a-zA-Z0-9_]/是(1 / 27.2-1 / 31.1)/ 10,000,000 = 0.000,000,000,461 s在我的计算机上更快


Non-ASCII positive match
               Rate      (?u:\w) [a-zA-Z0-9_]     (?aa:\w)
(?u:\w)      2.97/s           --        -100%        -100%
[a-zA-Z0-9_] 3349/s      112641%           --          -9%
(?aa:\w)     3664/s      123268%           9%           --

哇。该测试似乎正在进行一些优化。也就是说,多次运行测试会产生极其一致的结果。 (其他测试也一样。)

当找到非ASCII字符的匹配项时,仅ASCII的\w会击败显式类。

/\w/aa是(1/3349-1/3664)/ 10,000,000 = 0.000,000,000,002,57 s在我的计算机上更快


Non-ASCII negative match
               Rate      (?u:\w) [a-zA-Z0-9_]     (?aa:\w)
(?u:\w)      2.66/s           --          -9%         -71%
[a-zA-Z0-9_] 2.91/s          10%           --         -68%
(?aa:\w)     9.09/s         242%         212%           --

如果找不到非ASCII字符的匹配项,则仅ASCII的\w会击败显式类。

/[a-zA-Z0-9_]/是(1 / 2.91-1 / 9.09)/ 10,000,000 = 0.000,000,002,34 s在我的计算机上更快


结论

  • 我很惊讶/\w/aa/[a-zA-Z0-9_]/之间存在任何差异。
  • 在某些情况下,/\w/aa更快;在其他情况下,/[a-zA-Z0-9_]/
  • /\w/aa/[a-zA-Z0-9_]/之间的差异非常小(小于1纳秒)。
  • 差异很小,您不必担心。
  • 尽管/\w/aa/\w/u之间的字符比前面的字符多4个数量级,但use strict; use warnings; use feature qw( say ); use Benchmarks qw( cmpthese ); my %pos_tests = ( '(?u:\\w)' => '/^\\w*\\z/u', '(?aa:\\w)' => '/^\\w*\\z/aa', '[a-zA-Z0-9_]' => '/^[a-zA-Z0-9_]*\\z/', ); my %neg_tests = ( '(?u:\\w)' => '/\\w/u', '(?aa:\\w)' => '/\\w/aa', '[a-zA-Z0-9_]' => '/[a-zA-Z0-9_]/', ); $_ = sprintf( 'use strict; use warnings; our $s; for (1..1000) { $s =~ %s }', $_) for values(%pos_tests), values(%neg_tests); local our $s; say "ASCII-only positive match"; $s = "J" x 10_000; cmpthese(-3, \%pos_tests); say ""; say "ASCII-only negative match"; $s = "!" x 10_000; cmpthese(-3, \%neg_tests); say ""; say "Non-ASCII positive match"; $s = "\N{U+0100}" x 10_000; cmpthese(-3, \%pos_tests); say ""; say "Non-ASCII negative match"; $s = "\N{U+2660}" x 10_000; cmpthese(-3, \%neg_tests); 和{{1}}之间的差异也很小。

{{1}}

  1. Unicode版本11。

答案 1 :(得分:3)

此答案基于Perl,但以下所有带标签的工具应非常相似。

\w字符类(用于“单词”字符)遵循Unicode规范,用于“单词”的字符属性。这包括太多的东西和复杂性,以至于很难指定包含属性的类别。请参见perlrecharclass中的“ 文字字符”,例如,请参见this post。有关背景,请参见perlunicodeperluniprops

简而言之,除非使用/a(或/aamodifier或语言环境,否则它超出了63个ascii字符。

但是,问题特别是关于性能。此时,应该期望不同的工具在行为上可能有所不同,并且可能会有很多不同,因为这取决于正则表达式的实现。这篇文章的其余部分专门针对Perl。

人们可能希望检查较小的集合可能更快,或者可能期望\w之类的结构具有优化功能。让我们来衡量,而不是猜测。以下是一个旨在寻找合理结果的粗略基准,省略了一些细微差别。

use warnings;
use strict;
use feature 'say';

use List::Util qw(shuffle);
use Benchmark qw(cmpthese);

my $run_for = shift // 3;  # seconds to run benchmark for

my $str = join '', (shuffle 'a'..'z', 'A'..'Z', 0..9, '_') x 100;

sub word_class {
    my $str = shift;
    my @m_1 = $str =~ /\w/g;
    return \@m_1;
}

sub char_class {
    my $str = shift;
    my @m_2 = $str =~ /[a-zA-Z0-9_]/g;
    return \@m_2;
}


cmpthese(-$run_for, {
    word => sub { my $res = word_class ($str) },
    char => sub { my $res = char_class ($str) },
});

使用[a-zA-Z0-9_]组装一个字符串,将其重新排列,然后重复100次。整个字符串在/g下,\w[a-zA-Z0-9_]下被逐字匹配。因此,在每种情况下都是一个正则表达式,并且已经过基准测试。

结果

      Rate char word
char 583/s   --  -1%
word 587/s   1%   --

在我的测试中,上述数字在任何一次运行中都高达2%。所以没有区别。

注意:我尝试将非ASCII字符添加到测试字符串中,没有明显区别。

注意:带有/g的正则表达式在char后面累积char(6300)匹配项,但是在单个引擎运行中。另一种选择是重复检查单个匹配项。它们并不相同,但是无论两者是否都会在\w[a-zA-Z0-9_]之间暴露出相当大的性能差异。

请为自己计时,并使用更适合您情况的字符串和样式。


以上基准旨在作为一项基本的粗略衡量标准。但是,负(失败)匹配项尤其明显地丢失了,因此,预计引擎将通过所有种测试模式的可能性。

我通过在目标字符串更改为上面调用基准测试例程来测试这一点

$str = join '', qw(! / \ { } ^ % @) x 1_000;

\w[a-zA-Z0-9_]下都将不匹配。结果

        Rate char word
char 72820/s   -- -19%
word 89863/s  23%   --
至少可以说,这令我感到惊讶。 \w集要大得多(请参见池上的答案),这必然意味着正在进行大量(或“神奇”)优化。

这证明了我的总体结论:总体而言,它们的性能足够接近,因此只需使用更合适的编码方式即可;或者,根据您的特定用例对其进行计时。

答案 2 :(得分:1)

我认为

\w应该取决于语言环境的设置;
LANG=
LC_CTYPE=
LC_ALL=
如果我的话那么真实,那么\w不仅应该是[A-Za-z_],还应该是许多其他UCS字符,
如果将其设置为LANG=en_US,Imho只是[A-Za-z_], 参见Explain the effects of export LANG, LC_CTYPE, LC_ALL

\d可以是原样,或者[0-9]取决于正则表达式引擎,
sed's \d甚至不能通过其[0-9]选项来-E,只有更好的正则表达式引擎才是,而由[0-9]表示的gnu用{{1 }}
恕我直言,为类集预设的所有正则表达式速记都比正常的[[:digit:]]类集要快
[]\w, \d
[A-Za-z_], [0-9]比[^ A-Za-z_]快,等等。