PHP Conference Japan 2024

性能

模式中可能出现的一些项目比其他项目效率更高。使用字符类(如 [aeiou])比使用一组备选方案(如 (a|e|i|o|u))效率更高。一般来说,提供所需行为的最简单结构通常是最有效的。Jeffrey Friedl 的书中包含大量关于优化正则表达式以提高性能的讨论。

当模式以 .* 开头并且设置了 PCRE_DOTALL 选项时,模式会被 PCRE 隐式地锚定,因为它只能匹配主题字符串的开头。但是,如果未设置 PCRE_DOTALL,则 PCRE 无法进行此优化,因为 . 元字符不会匹配换行符,如果主题字符串包含换行符,则模式可能从其中一个换行符之后的字符开始匹配,而不是从开头开始匹配。例如,模式 (.*) second 匹配主题 "first\nand second"(其中 \n 代表换行符),第一个捕获的子字符串为 "and"。为了做到这一点,PCRE 必须在主题中的每个换行符之后重新尝试匹配。

如果您对不包含换行符的主题字符串使用此类模式,则通过设置 PCRE_DOTALL 或以 ^.* 开头模式以指示显式锚定可以获得最佳性能。这样可以防止 PCRE 沿主题扫描以查找要重新开始的换行符。

注意包含嵌套的不定重复的模式。当应用于不匹配的字符串时,这些模式可能需要很长时间才能运行。考虑模式片段 (a+)*

这可以以 33 种不同的方式匹配 "aaaa",并且随着字符串变长,此数字会迅速增加。(* 重复可以匹配 0、1、2、3 或 4 次,并且对于除 0 之外的每种情况,+ 重复可以匹配不同次数。)当模式的其余部分使得整个匹配将失败时,PCRE 原则上必须尝试所有可能的变体,这可能需要非常长的时间。

一种优化可以捕获一些更简单的案例,例如 (a+)*b,其中后面跟着一个文字字符。在开始标准匹配过程之前,PCRE 检查主题字符串中后面是否有 "b",如果没有,则立即失败匹配。但是,当没有后续文字时,无法使用此优化。您可以通过比较 (a+)*\d 与上述模式的行为来了解差异。当应用于整行 "a" 字符时,前者几乎会立即失败,而后者在字符串长度超过约 20 个字符时则需要相当长的时间。

添加注释

用户贡献的注释 1 条注释

1
arthur200126 at gmail dot com
10 个月前
> 注意包含嵌套的不定重复的模式。当应用于不匹配的字符串时,这些模式可能需要很长时间才能运行。

说它需要“很长时间”是轻描淡写:所需时间将呈指数增长,具体为 2^n,其中 n 是“a”字符的数量。如果在用户提供输入上运行此类表达式,则此行为可能导致“正则表达式拒绝服务”(ReDoS)。

为避免受到 ReDoS 的影响,请执行以下三件事之一(或可能不止一件)

* 编写您的表达式使其不受影响。 https://regexper.cn/redos.html 是一个很好的资源(PHP/PCRE 中提供了“原子”和“独占”选项)。如果您的肉眼无法发现所有问题,请使用“ReDoS 检测器”或“正则表达式 linter”。
* 为 preg_match 设置一些限制。在 https://php.net/manual/en/pcre.configuration.php. 上提到的值上使用 `ini_set(...)`。降低限制可能会导致正则表达式失败,但这通常比使整个服务器停滞更好。
* 使用不同的正则表达式实现。过去曾经有一个 RE2 扩展;现在没有了!
To Top