Почему `s+ 'намного быстрее `чем` ss*' в этом регулярном выражении Perl?
почему замена s* (или даже ss*) С s+ привести к такому ускорению для этого ввода?
use Benchmark qw(:all);
$x=(" " x 100000) . "_n";
$count = 100;
timethese($count, {
'/ss*n/' => sub { $x =~ /ss*n/ },
'/s+n/' => sub { $x =~ /s+n/ },
});
я заметил медленное регулярное выражение s/s*ns*/n/g в моем коде-когда дается входной файл 450KB, состоящий из множества пробелов с несколькими не-пробелами здесь и там, и финальной новой строкой в конце - регулярное выражение зависало и никогда не заканчивалось.
я интуитивно заменил регулярное выражение на s/s+n/n/g; s/ns+/n/g; и все стало что ж.
но почему это так быстро? После использования re Debug => "EXECUTE" я заметил s+ версия каким-то образом оптимизирована для запуска только в одной итерации:http://pastebin.com/0Ug6xPiQ
Matching REx "s*n" against " _%n"
Matching stclass ANYOF{i}[x09x0ax0cx0d ][{non-utf8-latin1-all}{unicode_all}] against " _%n" (9 bytes)
0 <> < _%n> | 1:STAR(3)
SPACE can match 7 times out of 2147483647...
failed...
1 < > < _%n> | 1:STAR(3)
SPACE can match 6 times out of 2147483647...
failed...
2 < > < _%n> | 1:STAR(3)
SPACE can match 5 times out of 2147483647...
failed...
3 < > < _%n> | 1:STAR(3)
SPACE can match 4 times out of 2147483647...
failed...
4 < > < _%n> | 1:STAR(3)
SPACE can match 3 times out of 2147483647...
failed...
5 < > < _%n> | 1:STAR(3)
SPACE can match 2 times out of 2147483647...
failed...
6 < > < _%n> | 1:STAR(3)
SPACE can match 1 times out of 2147483647...
failed...
8 < _> <%n> | 1:STAR(3)
SPACE can match 1 times out of 2147483647...
8 < _> <%n> | 3: EXACT <n>(5)
9 < _%n> <> | 5: END(0)
Match successful!
Matching REx "s+n" against " _%n"
Matching stclass SPACE against " _" (8 bytes)
0 <> < _%n> | 1:PLUS(3)
SPACE can match 7 times out of 2147483647...
failed...
Я знаю, что Perl 5.10 + немедленно завершит регулярное выражение (без его запуска), если новая строка отсутствует. Я подозреваю, что он использует местоположение новой строки, чтобы уменьшить объем поиска, который он делает. Для всех случаев выше это, кажется, умно уменьшить отступление участвует (обычно /s*n/ против строки пробелов потребуется экспоненциальное время). Может ли кто-нибудь предложить понимание того, почему s+ версия намного быстрее?
также обратите внимание, что s*? не предлагает какого-либо ускорения.
4 ответа:
когда есть узел "плюс" (например
\s+) в начале шаблона и узел не соответствует, механизм регулярных выражений пропускает вперед к точке отказа и пытается снова; с\s*, С другой стороны, движок продвигает только один символ за раз.Ив Ортон прекрасно объясняет эту оптимизацию здесь:
оптимизация класса запуска имеет два режима: "попробуйте каждую допустимую стартовую позицию" (doevery) и " режим флип-флопа" (!doevery), где он пробует только первую допустимую стартовую позицию в последовательности.
рассмотреть вопрос о /(\Д+)х/ и строку "123456Y", теперь мы знаем, что если мы не в состоянии соответствовать X после согласования "123456", то мы также не матч за "23456" (при условии, что никакие злые трюки в месте, которое отключить оптимизацию в любом случае), поэтому мы знаем, что можем пропустить вперед до тех пор, пока /не/ и только потом начать искать реальный матч. Это режим триггера.
/\s+/запускает режим триггера;/\s*/,/\s\s*/и/\s\s+/нет. Эта оптимизация не может быть применена к" звездным " узлам, таким как\s*потому что они могут соответствовать нулевым символам, поэтому сбой в одной точке последовательности не указывает на сбой позже в той же последовательности.
вы можете увидеть это в выходных данных отладки для каждого регулярного выражения. Я выделил пропущенные символы
^. Сравните это (пропускает четыре символа одновременно):$ perl -Mre=Debug,MATCH -e'"123 456 789 x" =~ /\d+x/' ... 0 <> <123 456 78> | 1:PLUS(3) POSIXD[\d] can match 3 times out of 2147483647... failed... 4 <123 > <456 789 x> | 1:PLUS(3) ^^^^ POSIXD[\d] can match 3 times out of 2147483647... failed... 8 <23 456 > <789 x> | 1:PLUS(3) ^^^^ POSIXD[\d] can match 3 times out of 2147483647... failed...для этого (пропускает один или два символа одновременно):
$ perl -Mre=Debug,MATCH -e'"123 456 789 x" =~ /\d*x/' ... 0 <> <123 456 78> | 1:STAR(3) POSIXD[\d] can match 3 times out of 2147483647... failed... 1 <1> <23 456 789> | 1:STAR(3) ^ POSIXD[\d] can match 2 times out of 2147483647... failed... 2 <12> <3 456 789 > | 1:STAR(3) ^ POSIXD[\d] can match 1 times out of 2147483647... failed... 4 <123 > <456 789 x> | 1:STAR(3) ^^ POSIXD[\d] can match 3 times out of 2147483647... failed... 5 <123 4> <56 789 x> | 1:STAR(3) ^ POSIXD[\d] can match 2 times out of 2147483647... failed... 6 <23 45> <6 789 x> | 1:STAR(3) ^ POSIXD[\d] can match 1 times out of 2147483647... failed... 8 <23 456 > <789 x> | 1:STAR(3) ^^ POSIXD[\d] can match 3 times out of 2147483647... failed... 9 <23 456 7> <89 x> | 1:STAR(3) ^ POSIXD[\d] can match 2 times out of 2147483647... failed... 10 <23 456 78> <9 x> | 1:STAR(3) ^ POSIXD[\d] can match 1 times out of 2147483647... failed... 12 <23 456 789 > <x> | 1:STAR(3) ^^ POSIXD[\d] can match 0 times out of 2147483647... 12 <23 456 789 > <x> | 3: EXACT <x>(5) 13 <23 456 789 x> <> | 5: END(0)обратите внимание, что оптимизация не применяется к
/\s\s+/, потому что\s+не находится в начале шаблона. Оба/\s\s+/(логически эквивалентно/\s{2,}/) и/\s\s*/(логически эквивалентно/\s+/), наверное может быть оптимизированы, хотя; это может иметь смысл спросить на perl5-porters будет ли это стоить усилий.
в случае, если вы заинтересованы, "режим триггера" включается путем установки
PREGf_SKIPфлаг на регулярном выражении при его компиляции. Смотрите код вокруг строк 7344 и 7405 в regcomp.c и строка 1585 в regexec.c в источнике 5.24.0.
во-первых, даже если полученное регулярное выражение не будет иметь того же значения, давайте уменьшим регулярные выражения до
\s*0и\s+0и использовать(" " x 4) . "_0"в качестве входных данных. Для скептиков, вы можете видеть здесь что отставание все еще присутствует.Теперь рассмотрим следующий код:
$x = (" " x 4) . "_ 0"; $x =~ /\s*0/; # The slow line $x =~ /\s+0/; # The fast lineкопать немного с
use re debugcolor;мы получаем следующий результат:Guessing start of match in sv for REx "\s*0" against " _0" Found floating substr "0" at offset 5... start_shift: 0 check_at: 5 s: 0 endpos: 6 checked_upto: 0 Does not contradict STCLASS... Guessed: match at offset 0 Matching REx "\s*0" against " _0" Matching stclass ANYOF_SYNTHETIC[\x09-\x0d 0\x85\xa0][{unicode_all}] against " _0" (6 bytes) 0 < _0>| 1:STAR(3) POSIXD[\s] can match 4 times out of 2147483647... failed... 1 < _0>| 1:STAR(3) POSIXD[\s] can match 3 times out of 2147483647... failed... 2 < _0>| 1:STAR(3) POSIXD[\s] can match 2 times out of 2147483647... failed... 3 < _0>| 1:STAR(3) POSIXD[\s] can match 1 times out of 2147483647... failed... 5 < _0>| 1:STAR(3) POSIXD[\s] can match 0 times out of 2147483647... 5 < _0>| 3: EXACT <0>(5) 6 < _0>| 5: END(0) Match successful! ----------------------- Guessing start of match in sv for REx "\s+0" against " _0" Found floating substr "0" at offset 5... start_shift: 1 check_at: 5 s: 0 endpos: 5 checked_upto: 0 Does not contradict STCLASS... Guessed: match at offset 0 Matching REx "\s+0" against " _0" Matching stclass POSIXD[\s] against " _" (5 bytes) 0 < _0>| 1:PLUS(3) POSIXD[\s] can match 4 times out of 2147483647... failed... Contradicts stclass... [regexec_flags] Match failedPerl кажется быть оптимизированы для отказа. Он будет сначала искать постоянные строки(которые потребляют только O (N)). Вот, он будет искать
0:Found floating substr "0" at offset 5...затем он с переменная часть регулярного выражения, соответственно
\s*и\s+, против всей минимальной строки для проверки:Matching REx "\s*0" against " _0" Matching stclass ANYOF_SYNTHETIC[\x09-\x0d 0\x85\xa0][{unicode_all}] against " _0" (6 bytes) Matching REx "\s+0" against " _0" Matching stclass POSIXD[\s] against " _" (5 bytes) # Only 5 bytes because there should be at least 1 "\s" charпосле этого он будет искать первую позицию встречи
stclassтребование, здесь в положении 0.
\s*0:
- начинается с 0, затем найдите 4 пробела провал;
- начинается с 1, Найти 3 пробела, а затем не удается;
- начинается с 2, найти 2 пробела, а затем не удается;
- начинается с 3, найти 1 пробел, а затем не удается;
- начинается с 4, Найти 0 пробелов, то не;
- найти точное
0\s+0:
- начинается с 0, найти 4 пробела, а затем не удается. Поскольку минимальное количество пробелов не совпадает, регулярное выражение не выполняется немедленно.
если вы хотите получить удовольствие от оптимизации регулярных выражений Perl, вы можете рассмотреть следующие регулярные выражения
/ *\nи/ * \n. На первый взгляд они выглядят одинаково, имеют одинаковый смысл... Но если вы запустите его против(" " x 40000) . "_\n"первый будет проверять все возможности, а второй будет искать" \n"и сразу фейл.в ванильном, неоптимизированном движке регулярных выражений, оба регулярных выражения могут вызвать катастрофическое отступление, так как они должны повторить шаблон, как он натыкается вдоль. Однако, в приведенном выше примере, второй не удастся с Perl, потому что он был оптимизирован, чтобы
find floating substr "0%n"
вы можете увидеть другой пример на блог Джеффа Этвуда.
обратите внимание также, что вопрос не о
\sрассмотрение, но любой шаблон, гдеxx*вместоx+посмотреть пример с 0s и регулярное выражение взрывчатого вещества кванторыС таким коротким примером поведение "находимо", но если вы начинаете играть со сложными шаблонами, это далеко не легко обнаружить, например: регулярное выражение зависает программа (100% загрузка ЦП)
The
\s+\nтребуется, чтобы символ, предшествующий\nэтоSPACE.по данным
use re qw(debug)компиляция устанавливает, что ей нужна прямая строка из известного числа пробелов, вплоть до подстроки\n, который сначала проверяется на входе. Затем он проверяет подстроку только с фиксированной длиной по отношению к оставшейся части ввода, терпя неудачу, когда дело доходит до_. Это единственная возможность проверить, независимо от того, сколько пространства на входе. (Когда есть больше_\nкаждый из них находится в состоянии сбоя одинаково непосредственно, на отладочный вывод.)посмотрел на это таким образом, это оптимизация, которую вы почти ожидаете, используя довольно специфический шаблон поиска и удачно с этим входом. За исключением тех случаев, когда они взяты по сравнению с другими двигателями, которые явно не делают такого рода анализ.
С
\s*\nэто не так. После того, как\nнайден, и предыдущий символ не является пробелом, поиск не завершился неудачей так как\s*ничего не позволяет (ноль символов). Также нет подстрок фиксированной длины, и это в игре с обратным отслеживанием.
Я не уверен во внутренних компонентах механизма регулярных выражений, но похоже, что он не признает это
\s+в некотором роде тот же как\s\s*, так как во втором он соответствует пространству, а затем пытается соответствовать постоянно растущему числу пространств, в то время как в первом он сразу же приходит к выводу, что совпадения нет.выход через
use re qw( Debug );ясно показывает это, используя гораздо короче строка:test_re.pl
#!/usr/bin/env perl use re qw(debug); $x=(" " x 10) . "_\n"; print '-'x50 . "\n"; $x =~ /\s+\n/; print '-'x50 . "\n"; $x =~ /\s\s*\n/; print '-'x50 . "\n";выход
Compiling REx "\s+\n" Final program: 1: PLUS (3) 2: SPACE (0) 3: EXACT <\n> (5) 5: END (0) floating "%n" at 1..2147483647 (checking floating) stclass SPACE plus minlen 2 Compiling REx "\s\s*\n" Final program: 1: SPACE (2) 2: STAR (4) 3: SPACE (0) 4: EXACT <\n> (6) 6: END (0) floating "%n" at 1..2147483647 (checking floating) stclass SPACE minlen 2 -------------------------------------------------- Guessing start of match in sv for REx "\s+\n" against " _%n" Found floating substr "%n" at offset 11... start_shift: 1 check_at: 11 s: 0 endpos: 11 Does not contradict STCLASS... Guessed: match at offset 0 Matching REx "\s+\n" against " _%n" Matching stclass SPACE against " _" (11 bytes) 0 <> < > | 1:PLUS(3) SPACE can match 10 times out of 2147483647... failed... Contradicts stclass... [regexec_flags] Match failed -------------------------------------------------- Guessing start of match in sv for REx "\s\s*\n" against " _%n" Found floating substr "%n" at offset 11... start_shift: 1 check_at: 11 s: 0 endpos: 11 Does not contradict STCLASS... Guessed: match at offset 0 Matching REx "\s\s*\n" against " _%n" Matching stclass SPACE against " _" (11 bytes) 0 <> < > | 1:SPACE(2) 1 < > < _> | 2:STAR(4) SPACE can match 9 times out of 2147483647... failed... 1 < > < _> | 1:SPACE(2) 2 < > < _> | 2:STAR(4) SPACE can match 8 times out of 2147483647... failed... 2 < > < _> | 1:SPACE(2) 3 < > < _%n> | 2:STAR(4) SPACE can match 7 times out of 2147483647... failed... 3 < > < _%n> | 1:SPACE(2) 4 < > < _%n> | 2:STAR(4) SPACE can match 6 times out of 2147483647... failed... 4 < > < _%n> | 1:SPACE(2) 5 < > < _%n> | 2:STAR(4) SPACE can match 5 times out of 2147483647... failed... 5 < > < _%n> | 1:SPACE(2) 6 < > < _%n> | 2:STAR(4) SPACE can match 4 times out of 2147483647... failed... 6 < > < _%n> | 1:SPACE(2) 7 < > < _%n> | 2:STAR(4) SPACE can match 3 times out of 2147483647... failed... 7 < > < _%n> | 1:SPACE(2) 8 < > < _%n> | 2:STAR(4) SPACE can match 2 times out of 2147483647... failed... 8 < > < _%n> | 1:SPACE(2) 9 < > < _%n> | 2:STAR(4) SPACE can match 1 times out of 2147483647... failed... 9 < > < _%n> | 1:SPACE(2) 10 < > <_%n> | 2:STAR(4) SPACE can match 0 times out of 2147483647... failed... Contradicts stclass... [regexec_flags] Match failed -------------------------------------------------- Freeing REx: "\s+\n" Freeing REx: "\s\s*\n"