Почему s/^\s + |\s + $//g; так медленнее, чем две отдельные замены?
Запись в Perl FAQ Как удалить пустое пространство с начала/конца строки? заявляет, что используя
s/^\s+|\s+$//g;
работает медленнее, чем выполнение в два этапа:
s/^\s+//;
s/\s+$//;
Почему этот комбинированный оператор заметно медленнее, чем отдельные (для любой входной строки)?
Ответы
Ответ 1
Среда выполнения регулярных выражений Perl выполняется намного быстрее при работе с подстроками "fixed" или "anchored", а не "floated". Подстрока фиксируется, когда вы можете заблокировать ее в определенном месте в исходной строке. Оба "^" и "$" обеспечивают это закрепление. Однако, когда вы используете альтернативу '|', компилятор не распознает выбор как фиксированный, поэтому он использует менее оптимизированный код для сканирования всей строки. И в конце процесса поиск фиксированных строк дважды намного, намного быстрее, чем поиск плавающей строки один раз. В соответствующей заметке чтение perl regcomp.c заставит вас ослепнуть.
Обновление:
Вот некоторые дополнительные подробности. Вы можете запустить 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
Обратите внимание на слово "привязано" на первом дампе.
Ответ 2
Другие ответы показали, что полностью привязанные регулярные выражения позволяют двигателю оптимизировать процесс поиска, фокусируясь только на начале или конце или строке. По-видимому, вы можете увидеть эффект этой оптимизации, сравнивая разницу в скорости двух подходов с использованием строк различной длины. По мере того как строка становится длиннее, "плавающее" регулярное выражение (с чередованием) страдает все больше и больше.
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% --
Ответ 3
Поскольку эти два метода логически эквивалентны, нет никаких причин для их отличия в производительности оценки. На практике, однако, некоторые двигатели не смогут определять оптимизацию в более сложных регулярных выражениях.
В этом случае объединенное регулярное выражение в целом не сохраняется, поэтому оно может потенциально совпадать в любой точке строки, а ^\s+
привязано к началу, так что это тривиально, и \s+$
привязывается в конце и предоставляет один символ для каждого символа с конца назад - хорошо оптимизированный движок распознает этот факт и будет соответствовать обратному, что делает его столь же тривиальным, как соответствие ^\s+
на обратной стороне вход.
Ответ 4
Если это действительно так, то это будет потому, что механизм регулярных выражений может оптимизировать лучше для отдельных регулярных выражений, чем для комбинированного.
Что вы подразумеваете под "заметно медленнее"?