Компиляция регулярных выражений, модификатор o, функция study, хронометраж
13.1. Компиляция и кэширование регулярных выражений
При первой встрече в тексте программы регулярного выражения Perl обрабатывает литерал этого выражения, а затем переводит его во внутреннюю форму (компилирует), которую уже использует механизм регулярных выражений.
При компиляции сначала происходит обработка литералов регулярного выражения.
- Отыскивается завершающий ограничитель регулярного выражения и читаются модификаторы всего регулярного выражения, которые стоят за этим ограничителем. Наличие модифиатора x учитывается при обработке литерала регулярного выражения.
- Если регулярное выражение имеет интерполируемые переменные, то вместо них подставляется их значение. При этом учитывается, что последовательности символов $|, $), … не являются переменными и не интерполируются.
- Далее обрабатываются конструкции изменения регистра /U, /l, … а также конструкции /Q…/E, но в тех частях регулярного выражения, которые получились от интерполяции переменных, эти конструкции не распознаются и не обрабатываются.
Потом, если не было замечено ошибок на шагах 1-3, регулярное выражение переводится в свою внутреннюю форму, готовую к применению механизмом регулярных выражений.
Но регулярное выражение может применяться в цикле, что при каждой его компиляции потребует существенных затрат времени процессора. Perl запоминает внутреннее представление регулярного выражения после его компиляции и привязывает его к соответствующему регулярному выражению в программе. В последующем при обращении к данному регулярному выражению повторной компиляции не происходит, а используется его готовое внутреннее представление. Это кэширование регулярного выражения делается для регулярных выражений, которые не содержат интерполируемых переменных или имеют модификатор o. Переменные, содержащиеся во встроенном коде, и динамические регулярные выражения на кэширование регулярного выражения не влияют, поскольку не интерполируются, а входят в исполняемый код, который не изменяется между обращениями к данному регулярному выражению. Кэш регулярных выражений имеет ограниченные возможности, поэтому кэшируются лишь десять регулярных выражений, которые использовались последними (хотя в точности этого числа кэшируемых регулярных выражений я не уверен).
Если регулярное выражение содержит интерполированную переменную, то в некоторых случаях Perl может проверить простым побайтовым сравнением, не изменилось ли содержимое этой переменной с прошлого применения регулярного выражения. Если оно не изменилось, то Perl не делает перекомпиляцию этого регулярного выражения.
Отказ от перекомпиляции регулярного выражения дает очень существенную экономию времени, поэтому используйте объекты регулярных выражений qr/…/. Они содержат уже откомпилированное и готовое к использованию регулярное выражение, представляя собой как бы автономный кэш регулярного выражения.
Если после интерполяции переменных регулярное выражение имеет пустое содержание, то вместо него применяется последнее успешно совпавшее регулярное выражение. Это можно использовать для оптимизации времени работы программы. Приведу такой пример:
my $a='abc'; my $b='cde'; my $re='(\w)'; $a =~ /$re/; print "$1\n"; $b =~ //; print $1;
a c
Пояснение: в операторе $a =~ /$re/; мы использовали регулярное выражение, которое совпало, и напечаталась буква a. Далее мы можем множество раз задавать пустое регулярное выражение //. Вместо него будет использоваться это последнее совпавшее кэшированное регулярное выражение. Это видно при печати буквы c. Если теперь изменить содержимое переменной $re, то это не повлияет на результат применения пустого регулярного выражения, потому что интерполированное значение переменной $re (т.е. внутреннее представление литерала (\w) ) уже находится в кэшированном регулярном выражении по умолчанию).
Эта оптимизация не является естественной и с приходом объектов регулярных выражений она устарела.
Если оператор поиска или замены не содержит ничего кроме объекта регулярного выражения:
my $re=/…/; … if ($_ =~ $re) … или if (m/$re/) …
то в этом операторе напрямую применяется откомпилированный объект регулярного выражения.
Здесь уместно вспомнить об опасности применения модификатора o с объектами регулярных выражений:
my $re=/…/o;
который дает неожиданный и неприятный эффект, описанный ранее.