PHP 日本研討會 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 則註解

arthur200126 at gmail dot com
10 個月前
> 請注意包含巢狀不定重複的模式。當應用於不匹配的字串時,這些模式可能需要很長時間才能執行。

說它需要「很長的時間」是一種保守的說法:所需的時間會呈指數增長,具體來說是 2^n,其中 n 是 "a" 字元的數量。如果您在使用者提供的輸入上執行此類運算式,則此行為可能會導致「正規表示式阻斷服務」(ReDoS)。

為了避免受到 ReDoS 的攻擊,請執行以下三件事中的一件(或可能不只一件)

* 編寫您的運算式,使其不會受到攻擊。https://regular-expressions.dev.org.tw/redos.html 是一個很好的資源(PHP/PCRE 中都提供「原子」和「擁有格」選項)。如果您的肉眼無法發現所有問題,請使用「ReDoS 偵測器」或「正規表示式檢查器」。
* 為 preg_match 設定一些限制。在 https://php.dev.org.tw/manual/en/pcre.configuration.php. 上提到的值使用 `ini_set(...)`。減少限制可能會導致正規表示式失敗,但這通常比讓整個伺服器停滯更好。
* 使用不同的正規表示式實作。以前有一個 RE2 擴充功能;但現在沒有了!
To Top