多正則表達式匹配 (Multiple Regular Expression Matching) 中的動態 DFA 算法

前一段時間,在將 多正則表達式匹配工具 用於數十萬任意的正則表達式時,之前一直擔憂的問題終於出現了:NFA 轉化 DFA 時的指數爆炸,那樣的 DFA 根本建立不出來,由於那些正則表達式之間有不可預料的各類交集!正則表達式

這個問題對我打擊很大,我甚至頓時以爲 多正則表達式匹配工具 徹底是個廢柴,最多,是個玩具!可是,只有挑戰,才能激勵人的鬥志,挖掘人的潛能。我想起了曾經對之不屑一顧的動態 DFA 匹配算法,以前我在研究 RE2 時知道,RE2 在動態 DFA 的內存用量達到限定時,會拋棄已經建立整個動態 DFA,由於 DFA 的狀態圖比較複雜,節點之間互相引用,沒法象普通的 Cache 同樣部分的進行 Swap ,直覺上對它就沒有好印象。算法

可是碰到組合爆炸這個魔咒,只有嘗試一下,就先用 RE2 中的 set 試一下,先從那數十萬正則表達式中任選一萬個,re2 執行卻是很快,但立馬就提示 DFA Out Of Memory 了,而後我嘗試將 re2 的內存設到 8G,仍是不行,浪費若干腦細胞,最後才驚奇地發現,Re2 的內存限制用的是 int 來表示的,而 int 的事實標準是32 bit,最大隻能是 2G-1!設成 2G-1以後,re2 運行正常,性能還不錯。工具

因而我開始加碼,將全部正則表達式都扔進去,果真,re2 崩了!性能

雖然如此,可是至少代表,動態 DFA 解決該問題是可行的,我開始實現個人計劃。一切都很順利:測試

以前我實現其餘算法時(r1303),擴展了自動機的字符集,而沒有在自動機的狀態上打專用 Tag,這個決定真是明智之舉,在這裏又用上了:google

ch=256 表示括號捕獲 用於我最先實現 One Pass 正則表達式的 DFA 算法
ch=257 表示全匹配 以前在一遍掃描中捕獲全部括號時,發現因括號位置太靠前而致使嚴重的性能問題,
就實現了兩遍掃描捕獲括號,第一遍只看全匹配,得到正則表達式ID,
第二遍專門針對具體正則ID捕獲括號,性能要好的多
ch=258 表示僞Epsilon轉移 這是實現動態 DFA 匹配的一個根基,不過這個僞 Epsilon 轉移只起到一個鏈表的做用,將全部子 DFA 的根(初始狀態)鏈起來


若是一切都照抄 re2 的實現,那不過是無聊的體力勞動。個人動態 DFA 實現,徹底創建在我以前的各類自動機算法之上,有了這個根基,個人實現相比 re2,有不少優點:spa

  1. 系統:如同一套數學理論,有定義、公理、定理,任何新的推論,都創建在以前已經確立的堅實的基礎之上
  2. 簡單:由於已經有了堅實的基礎,並不須要引入多少全新的東西,就能解決新的問題
  3. 可靠:不須要驗證已有的代碼,只要證實新加入的代碼是正確的,整個系統就是正確可靠的
  4. 高效:已有的代碼已足夠高效,只要新的代碼沒有冗餘計算,最終的算法必然是高效的
事實證實了這些:專門用於實現動態 DFA 的代碼也就 500 行左右,包含了算法的全部細節:
  1. 一個基於已有 DenseDFA 的 DynDFA 基礎類
  2. 用於 subset 查找的專用 hash table 實現,包括 find, insert, hash resize, hash function, hash equal, hash cache, ...
  3. 動態 DFA 的 warm up ,將靠近完整 DFA 的根(初始狀態) 附近的狀態構造出來做爲熱點,re2 中則徹底沒有考慮 warm up
  4. 動態 DFA 內存超限時,只拋棄不在 warm up 集合的那些狀態,而不像 re2 同樣拋棄整個動態 DFA
  5. 動態 DFA 的基礎 NFA 只是簡單地用 ch=258 將全部子 DFA 併到一塊兒,並且每一個子 DFA 都是最小化的,同時全部的子 DFA 共享了相同的尾
  6. 動態 DFA 直接引用做爲捕獲括號(submatch) 的子 DFA
最後的測試代表,個人算法比 RE2 要快 4~7 倍,在達到這個性能的同時,內存用量還要小 5~8 倍,而且,對於讓 re2 崩掉的那幾十萬個正則表達式,運行徹底正常,同時,還能捕獲括號(submatch)。

使用:在  多正則表達式匹配工具 中,命令行參數 -D 表示生成的自動機匹配時用動態 DFA。
相關文章
相關標籤/搜索