Эффективность слов RegEx: \ w vs [a-zA-Z0-9_] - PullRequest
2 голосов
/ 16 апреля 2019

Я хотел бы знать список символов, которые проходит \w, это просто [a-zA-Z0-9_] или есть еще символы, которые он может охватить?

Я задаю этот вопрос, потому что на основе это , \d отличается от [0-9] и менее эффективно .

\w против [a-zA-Z0-9_]: какой из них может быть быстрее в больших масштабах?

Ответы [ 3 ]

5 голосов
/ 16 апреля 2019

[ Этот ответ зависит от Perl. Приведенная информация может не относиться к PCRE или движку, используемому другими тегированными языками. /\w/aa (фактический эквивалент /[a-zA-Z0-9_]/) обычно быстрее, но не всегда. Тем не менее, разница настолько минимальна (менее 1 наносекунды на проверку), что это не должно вызывать беспокойства. Чтобы поместить его в контекст, требуется гораздо больше времени, чтобы вызвать подпрограмму или запустить механизм регулярных выражений.

То, что следует, подробно описывает это.


Прежде всего, \w не совпадает с [a-zA-Z0-9_] по умолчанию. \w соответствует каждому буквенная, цифровая, маркированная и соединительная пунктуация Unicode Code Point. Их 119 821! [1] Определение самого быстрого неэквивалентного кода не имеет смысла.

Однако использование \w с /aa гарантирует, что \w соответствует только [a-zA-Z0-9_]. Вот что мы собираемся использовать для наших тестов. (На самом деле, мы будем использовать оба.)

(Обратите внимание, что каждый тест выполняет 10 миллионов проверок, поэтому скорость 10,0 / с фактически означает 10,0 миллионов проверок в секунду.)


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.
3 голосов
/ 16 апреля 2019

Этот ответ основан на Perl, но все инструменты с тегами должны быть очень похожи в следующем.

Класс символов \w (для символа «слово») соответствует спецификациям Юникода для свойств символа слова. Это включает в себя так много вещей и сложности, что сложно определить категории включаемых свойств. См. " Символы слова " в perlrecharclass и, например, в этом посте . См. perlunicode и perluniprops для фона.

Короче говоря, это далеко за пределы 63 символов ascii, если только не используется модификатор /a (или /aa) или локали.

Тем не менее, речь идет конкретно о производительности. На этом этапе следует ожидать, что различные инструменты будут различаться в поведении, и, возможно, во многих, поскольку это зависит от реализации регулярных выражений. Остальная часть этого поста специально для 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 накапливает совпадения (6300) char после char, но за один запуск двигателя. Другой вариант - повторно проверять совпадение. Они не одинаковы, но независимо от того, что оба будут представлять разницу в производительности между \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 настолько велик (см. Ответ икегами), что это должно подразумевать, что происходят серьезные (или «магические») оптимизации.

Это подтверждает мой общий вывод: в целом их производительность достаточно близка, поэтому просто используйте то, что является более подходящим для кодирования; Или время в вашем конкретном сценарии использования.

1 голос
/ 16 апреля 2019

\w насколько я предполагаю, должно зависеть от настроек среды локали, таких как:
LANG=
LC_CTYPE=
LC_ALL=
, если мои так верны, то \wдолжно быть не просто [A-Za-z_], как многие другие символы UCS,
, если установлено LANG=en_US Imho просто [A-Za-z_], см. Объясните эффекты экспортаLANG, LC_CTYPE, LC_ALL

\d может быть как есть или [0-9] зависит от движка регулярных выражений, конечно,
sed's \d не может быть [0-9] дажеего опция -E, так будет только лучший движок регулярных выражений, вместо этого [0-9], представленный gnu sed с [[:digit:]]
Imho, все пресеты с регулярными выражениями для набора классов быстрее, чем обычно [] набор классов
\w, \d быстрее, чем [A-Za-z_], [0-9] соответственно
\W быстрее, чем [^ A-Za-z_] и т. Д.

...