Почему s/^\\s + |\s+ $//g; настолько медленнее, чем две отдельных замены?

Запись FAQ Perl, Как я разделяю пробел с начала/конца строки? состояния то использование

s/^\s+|\s+$//g;

медленнее, чем выполнение его на двух шагах:

s/^\s+//;
s/\s+$//;

Почему это - объединенный оператор заметно медленнее, чем отдельные (для какой-либо входной строки)?

13
задан brian d foy 23 February 2010 в 20:57
поделиться

4 ответа

Среда выполнения регулярных выражений Perl работает намного быстрее при работе с «фиксированными» или «привязанными» подстроками, а не с «плавающими» подстроками. Подстрока фиксируется, если вы можете привязать ее к определенному месту в исходной строке. И '^', и '$' обеспечивают такую ​​привязку. Однако, когда вы используете чередование '|', компилятор не распознает выбор как фиксированный, поэтому он использует менее оптимизированный код для сканирования всей строки. И в конце процесса дважды искать фиксированные строки намного быстрее, чем один раз искать плавающую строку. Кстати, чтение regcomp.c на perl заставит вас ослепнуть.

Обновление : Вот некоторые дополнительные сведения. Вы можете запустить perl с флагом '-Dr', если вы скомпилировали его с поддержкой отладки, и он будет выгружать данные компиляции регулярного выражения.Вот что вы получите:

~# debugperl -Dr -e 's/^\s+//g'
Compiling REx `^\s+'
size 4 Got 36 bytes for offset annotations.
first at 2
synthetic stclass "ANYOF[\11\12\14\15 {unicode_all}]".
   1: BOL(2)
   2: PLUS(4)
   3:   SPACE(0)
   4: END(0)
stclass "ANYOF[\11\12\14\15 {unicode_all}]" anchored(BOL) minlen 1

# debugperl -Dr -e 's/^\s+|\s+$//g'
Compiling REx `^\s+|\s+$'
size 9 Got 76 bytes for offset annotations.

   1: BRANCH(5)
   2:   BOL(3)
   3:   PLUS(9)
   4:     SPACE(0)
   5: BRANCH(9)
   6:   PLUS(8)
   7:     SPACE(0)
   8:   EOL(9)
   9: END(0)
minlen 1 

Обратите внимание на слово «привязанный» в первом дампе.

12
ответ дан 1 December 2019 в 21:37
поделиться

В других ответах говорится, что полностью привязанные регексы позволяют системе оптимизировать процесс поиска, фокусируясь только на начале или конце строки. Похоже, что эффект этой оптимизации можно увидеть, сравнив разницу в скорости двух подходов при использовании строк различной длины. По мере увеличения длины строки "плавающий" регекс (использующий чередование) страдает все больше и больше.

use strict;
use warnings;
use Benchmark qw(cmpthese);

my $ws = "   \t\t\n";

for my $sz (1, 10, 100, 1000){
    my $str = $ws . ('Z' x $sz) . $ws;
    cmpthese(-2, {
        "alt_$sz" => sub { $_ = $str; s/^\s+|\s+$//g },
        "sep_$sz" => sub { $_ = $str; s/^\s+//; s/\s+$// },
    });
}

           Rate alt_1 sep_1
alt_1  870578/s    --  -16%
sep_1 1032017/s   19%    --

            Rate alt_10 sep_10
alt_10  384391/s     --   -62%
sep_10 1010017/s   163%     --

            Rate alt_100 sep_100
alt_100  61179/s      --    -92%
sep_100 806840/s   1219%      --

             Rate alt_1000 sep_1000
alt_1000   6612/s       --     -97%
sep_1000 261102/s    3849%       --
9
ответ дан 1 December 2019 в 21:37
поделиться

Поскольку эти два метода логически эквивалентны, нет никаких внутренних причин для того, чтобы они отличались по производительности оценки. Однако на практике некоторые движки не смогут заметить оптимизацию в более сложных регексах.

В данном случае комбинированный регекс в целом не привязан, поэтому потенциально может совпасть в любой точке строки, в то время как ^\s+ привязан в начале, поэтому его совпадение тривиально, а \s+$ привязан в конце, и предоставляет один символьный класс для каждого символа с конца назад - хорошо оптимизированный движок распознает этот факт и будет сопоставлять в обратном направлении, что делает его таким же тривиальным, как ^\s+ на обратном входе.

3
ответ дан 1 December 2019 в 21:37
поделиться

Если это действительно так, то это потому, что regex движок способен оптимизировать лучше для отдельных regex, чем для объединенных.

Что вы подразумеваете под "заметно медленнее"?

1
ответ дан 1 December 2019 в 21:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: