【问题标题】:RegEx word performance: \w vs [a-zA-Z0-9_]正则表达式词性能:\w vs [a-zA-Z0-9_]
【发布时间】:2019-09-06 00:55:12
【问题描述】:

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

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

\w vs [a-zA-Z0-9_]:在大规模情况下,哪一个可能更快?

【问题讨论】:

标签: c# php regex perl pcre


【解决方案1】:

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

\w 字符类(用于“单词”字符)遵循“单词”的 字符属性 的 Unicode 规范。这包括太多的东西和复杂性,以至于指定所包含属性的类别是一个挑战。例如,请参阅 perlrecharclass 和 this post 中的“单词字符”。有关背景信息,请参阅 perlunicode 和 perluniprops。

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

但是,这个问题专门针对性能。在这一点上,应该预计不同的工具会在行为上有所不同,并且可能会有很多差异,因为这取决于正则表达式的实现。这篇文章的其余部分是针对 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_] 下逐个字符匹配。所以在每种情况下它都是一个正则表达式,并且这些都是基准测试的。

结果

评价 char word 字符 583/s -- -1% 字 587/s 1% --

在我的测试中,无论哪种方式,上述数字都上升了 2%。所以没有区别。

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

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

请自行安排时间,使用更适合您情况的字符串和模式。


上述基准是一个基本的、粗略的衡量标准。然而,显着缺失的是否定(失败)匹配,因此引擎预计会通过 所有 种可能性来测试模式。

我通过在目标字符串上调用上面的基准测试例程来进行测试

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

在\w 和[a-zA-Z0-9_] 下都将无法匹配。结果

评价 char word 字符 72820/s -- -19% 字 89863/s 23% --

至少可以说,这对我来说是一个惊喜。 \w 集要大得多(请参阅 ikegami 答案),这一定意味着正在进行大量(或“神奇”)优化。

这强化了我的总体结论:这些性能通常足够接近,所以只需使用更合适的编码方式;或者,在您的特定用例中计时。

【讨论】:

  • 这是一个相当差的基准。它只测试一种数据,它只检查正匹配,它并没有真正检查被基准测试的两件事之间的差异,因为它们迷失在调用 sub 和启动正则表达式引擎所需的时间噪音中.
  • Re "[区别在于] 迷失在调用 sub 和启动正则表达式引擎所需的时间的噪音中",我想这是有意义的。它说明了优化这个问题的重要性。
  • @ikegami Re "相当可怜" -- 我完全不同意。这并不意味着详尽无遗,而是一个合理的粗略做法;它已完全记录在案——我确切地说明测试了什么/如何测试。这个发现(“没关系”)是合理的。 (2)“只测试一种数据”——我尝试了非ascii数据,没有任何区别;详细说明这将违反其“rought take”性质(3)“仅检查正匹配”——它检查近 10K 混合 ascii 的字符串;有很多失误。再次,这是一个粗略的看法。 (4) “迷失在喧嚣中”——所以没关系
  • @ikegami 郑重声明:感谢您的详细回答(一如既往的好),并且非常感谢您对此的反馈/批评。我只是不同意“差”的资格;我认为它是有用的。
  • @ikegami "基准测试很容易出错" -- 是的,几十年来一直在处理它们......这就是为什么我一直在讨论这个问题,因为我认为这个是合理的(因为没有缺陷/错误/...),如果不是,我想知道为什么。说到这一点——\w 的阴性(失败)测试更快,这只是告诉我幕后有太多“魔法”(优化)。
【解决方案2】:

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

/\w/aa(实际相当于/[a-zA-Z0-9_]/)通常更快,但并非总是如此。也就是说,差异是如此之小(每次检查不到 1 纳秒),这不应该是一个问题。把它放在上下文中,调用 sub 或启动正则表达式引擎需要更长的时间。

接下来会详细介绍这一点。


首先,\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 秒


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 秒


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 秒


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 秒


结论

  • 我很惊讶/\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. Unicode 版本 11。

【讨论】:

  • 我认为这不会产生影响,但只要您不使用/i,/a 就足以使\w 完全等同于[a-zA-Z0-9_]。跨度>
  • @Grinnz,我保持简单。我说过“使用\w 和/aa 可以确保\w 只匹配[a-zA-Z0-9_]”,如果不偏离主题,就无法为/a 做出这样的声明。
【解决方案3】:

\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 即使通过其-E 选项也不能是[0-9],只有更好的正则表达式引擎才会是这样,而不是 [0-9] 由 gnu 代表 sed 和 [[:digit:]]
恕我直言,为类集预设的所有正则表达式速记都快于正常 [] 类集
\w, \d 分别比 [A-Za-z_], [0-9] 快
\W 比 [^A-Za-z_] 快,依此类推.

【讨论】:

  • Re "\w 据我所知,应该取决于语言环境设置",在 Perl 中不是(除非使用/l)跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-07
  • 2010-12-11
  • 2021-11-09
  • 1970-01-01
  • 2019-05-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多