Rendimiento
Ciertos elementos que pueden aparecer en patrones son más eficientes
que otros. Es más eficiente usar una clase carácter
como [aeiou] en vez de un conjunto de alternativas como (a|e|i|o|u).
En general, la construcción más simple que proporciona el
comportamiento requerido es normalmente la más eficiente. El libro
de Jeffrey Friedl contiene muchas discusiones sobre la optimización de
expresiones regulares para un rendimiento eficiente.
Cuando un patrón comienza con .* y la opción PCRE_DOTALL está
establecida, el patrón es anclado implícitamente por PCRE, ya que
sólo puede coincidir con el inicio de una cadena objetivo. Sin embargo, si
PCRE_DOTALL
no está establecido, PCRE no puede realizar esta optimización,
ya que el metacarácter . no coincide entonces con una nueva línea,
y si la cadena objetivo contine nuevas líneas, el patrón puede
coincidir con el carácter inmediatemente siguiente a ellas
en vez de con el inicio absoluto. Por ejemplo, el patrón
(.*) segundo
coincide con el sujeto "primero\ny segundo" (donde \n significa
un carácter de nueva línea), siendo "y" la primera subcadena capturada.
Para llevar a cabo esto, PCRE ha de re-intentar la comparación
comenzando después de cada nueva línea del sujeto.
Si está usando un patrón similar con cadenas objetivo que no
contienen nuevas líneas, el mejor rendimiento se obtiene
estableciendo PCRE_DOTALL,
o comenzando el patrón con ^.* para
indicar el anclado explícito. Esto salva a PCRE de tener que
escanear el sujeto buscando una nueva línea desde donde reiniciar.
Tenga cuidado con los patrones que contienen repeticiones anidadas indefinidas.
Éstos pueden tomar mucho tiempo al ejecutarse cuando se aplican a cadenas
que no coinciden. Considere el fragmento de patrón
(a+)*
Esto puede coincidir con "aaaa" de 33 maneras diferentes, y este número
se incrementa muy rápidamente mientras la cadena se hace más larga. (La
repetición * puede coincidir 0, 1, 2, 3, o 4 veces, y para cada uno de
estos casos distintos de 0, las repeticiones de + pueden coincidir diferente
número de veces.) Cuando el resto del patrón es tal
que la comparación completa falla, PCRE tiene como principio
el intentar cada posible variación, y esto puede tomar un
tiempo extremadamente largo.
Una optimización toma algunos de los casos más simples como
(a+)*b
donde sigue un carácter literal. Antes de emprender el
procedimiento de comparación estándar, PCRE verifica que hay una "b"
posterior en la cadena objetivo, y si no la hay, falla
la comparación inmediatamente. Sin embargo, cuando no hay un literal
siguiente, no se puede usar esta optimización. Puede ver la
diferencia comparando el comportamiento de
(a+)*\d
con el patrón anterior. El primero otorga un fallo casi
instantáneamente cuando se aplica a una línea llena de caracteres "a",
mientras que el segundo toma un tiempo apreciable con cadenas
con más de de 20 caracteres.
arthur200126 at gmail dot com ¶10 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!