CascadiaPHP 2024

Производительность

Некоторые элементы, которые встречаются в шаблонах, более эффективны, чем другие. Например, символьный класс [aeiou] работает гораздо эффективнее набора альтернатив (a|e|i|o|u). Как правило, более простая конструкция наболее эффективна. Книга Джеффри Фридла содержит много обсуждений вопроса оптимизации регулярных выражений.

Если шаблон начинается с .* и включили флаг PCRE_DOTALL, шаблон неявно заякоривается, поскольку шаблон может совпадать только в начале строки. Но если модификатор PCRE_DOTALL не включили, PCRE не может выполнить соответствующую оптимизацию, поскольку в этой ситуации метасимвол «.» не соответствует символу начала строки (если обрабатываемые данные содержат переводы строк, такой шаблон может соответствовать шаблону не от начала строки, а от позиции непосредственно после перевода строки). Например, применяя шаблон (.*) second к строке «first\nand second» (где \n — символ перевода строки), значение, которое захватил первый подшаблон, будет «and». Чтобы обработать все возможные точки соответствия, модуль PCRE пытается сопоставить шаблон после каждого символа перевода строки.

При работе с подобными шаблонами и обработке данных без переводов строк для лучшей производительности включают модификатор PCRE_DOTALL, либо начинают шаблон с циркумфлекса ^.* для явного заякоривания. Это защитит модуль PCRE от поиска символов новых строк и дополнительных попыток сопоставить шаблон с каждой такой найденной позицией.

Избегайте шаблонов, которые содержат вложенные неограниченные повторения. Сопоставление таких шаблонов со строками, которые не содержат совпадений, занимает длительное время. Рассмотрим пример шаблона (a+)*

Он может соответствовать значению «aaaa» тридцатью тремя различными способами, и эта цифра очень быстро растёт при увеличении строки. (В примере квантификатор * может совпадать 0, 1, 2, 3 или 4 раза, и для каждого такого случая, кроме нуля, квантификатор + также может совпадать различное число раз.) Если остаток шаблона таков, что все совпадение терпит неудачу, модуль PCRE должен попробовать все возможные варианты совпадения, что может потребовать огромного количества времени.

За счёт оптимизации отлавливают наиболее простые случаи наподобие (a+)*b где следом идёт литеральный символ. Перед началом стандартной процедуры поиска модуль PCRE проверяет в следующей подстроке наличие символа «b», и если символа нет, попытка сопоставления немедленно завершается неудачей. Однако, когда последующего литерала нет, оптимизация не может быть применена. Разница заметна при сравнении поведения шаблона (a+)*\d с поведением приведённого выше шаблона. Первый определяет невозможность сопоставления практически сразу, при сопоставлении со строкой, которая состоит из символов «a», тогда как второй тратит длительное время на поиск в строках с длиной больше 20 символов.

add a note

User Contributed Notes 1 note

up
1
arthur200126 at gmail dot com
5 months ago
> Beware of patterns that contain nested indefinite repeats. These can take a long time to run when applied to a string that does not match.

To say that it takes a "long time" is an understatement: the time taken would be exponential, specifically 2^n, where n is the number of "a" characters. This behavior could lead to a "regular expression denial of service" (ReDoS) if you run such a expression on user-provided input.

To not be hit by ReDoS, do one (or maybe more than one) of the three things:

* Write your expression so that it is not vulnerable. https://www.regular-expressions.info/redos.html is a good resource (both the "atomic" and "possessive" options are available in PHP/PCRE). Use a "ReDoS detector" or "regex linter" if your eyeballs can't catch all the issues.
* Set up some limits for preg_match. Use `ini_set(...)` on the values mentioned on https://www.php.net/manual/en/pcre.configuration.php. Reducing the limits might cause regexes to fail, but that is usually better than stalling your whole server.
* Use a different regex implementation. There used to be an RE2 extension; not any more!
To Top