2012-09-14 25 views
18

每次我必須對字符串進行簡單的遏制或替換操作時,我所搜索的術語是固定值,我發現如果我接受我的示例輸入並對其進行分析,則使用編譯好的常規表達式幾乎總是比使用String類中的等價方法更快。爲什麼C#編譯正則表達式比等效的字符串方法更快?

我已經嘗試比較多種方法(hs是「大海撈針」來搜索,ndl是「針」來搜索,repl是重置價值regex總是與RegexOptions.Compiled選項創建):

  • hs.Replace(ndl, repl) VS regex.Replace(hs, repl)
  • hs.Contains(ndl) VS regex.IsMatch(hs)

我已經發現了不少的討論重點的兩種技術,這更快(123,和其他的負載),但是這些討論似乎總是集中在:

  1. 使用字符串版本的簡單複雜操作的操作和正則表達式(從原始性能角度來看,這似乎不一定是個好主意),或者運行一個測試並比較兩者(對於等效測試,正則表達式版本似乎不是總是表現得更好)。

我不明白這可能是這樣的情況:正則表達式引擎如何比較任何兩個字符串的子字符串匹配比等效的字符串版本更快?這似乎適用於非常小或非常大的搜索空間,或搜索詞大小的搜索詞,或者搜索詞在搜索空間的早期還是晚期發生。

那麼,爲什麼是正則表達式更快?


*事實上,只有情況下,我已經成功地顯示字符串版本比編譯正則表達式搜索一個空字符串時,速度更快!任何其他情況下,從單字符字符串到非常長的字符串都會被編譯後的正則表達式比等效字符串方法更快地處理。


更新:加入到澄清,我期待在這裏搜索項在編譯時已知病例的條款。對於動態操作或一次性操作,編譯正則表達式的開銷會傾向於傾斜結果而偏向字符串方法。

+0

我相信你的更好的問題可能是「如果正則表達式更快,爲什麼不使用正則表達式?」。無論如何,我唯一的猜測將會涉及以下任何一項:1.正則表達式編譯需要一些額外的時間,你不會考慮,或2.正則表達式編譯/處理吃更多的內存或什麼,你會得到一個時空權衡。 – zebediah49

+0

@ zebediah49我認爲你是對的,我的問題導致了這個問題 - 但我真的很感興趣爲什麼或如何正則表達式表現更好。 –

+0

近距離選票來自哪裏? –

回答

14

我不明白這可能是這樣的情況:正則表達式引擎如何比較任何兩個字符串的子字符串匹配比等效的字符串版本更快?

我能想到的原因有兩個:

  1. 正則表達式是使用一些智能算法等Boyer Moore(O(M/N)),而簡單的字符串操作簡單地將針進行比較,以各位置中的乾草堆(O(N * M))。
  2. 他們並不是真的在做同樣的事情。例如,可以進行文化不變匹配,而另一個可以進行文化相關匹配,這可能會導致性能差異。
+0

只是要發表評論:) IIRC正則表達式編譯成一些BM代碼,用於簡單的字符串搜索。 – leppie

+2

+1。 O(N/M)?我以爲O(N + M)...鏈接? –

+3

@AlexeiLevenkov Boyer Moore是O(m/n)。 Knuth-Morris-Pratt是O(n + m) –

6

爲基類庫團隊wrote

在[RegexOptions.Compiled的情況下],我們首先做解析 爲操作碼的工作。然後我們還做了更多的工作,使用Reflection.Emit將這些操作碼轉換爲實際的IL。正如您可以想象的那樣,該模式的交易時間爲 ,可以提高運行時間:實際上,編譯 的啓動時間大約要延長一個數量級,但運行性能會提高30%。

但是,你overloking一件重要的事情:模式是固定的。請注意,情況並非總是如此。你不能在運行時改變它!有些情況下,靈活性會下降超過30%的性能增益。

+0

這是一個很好的鏈接,謝謝!我知道這個模式是固定的 - 我在zebediah49的評論之後更新了這個問題,試圖讓這個問題更加清楚。 –

1

從我自己的經驗,每當我爲基準的正則表達式的解決方案VS簡單的字符串操作自定義分析器,正則表達式的解決方案是有點慢。那是因爲他們經常遭受回溯。

但出於好奇,我寫了一個小測試,看看你說的是否真的是最簡單的例子。

這裏是我的測試...

private static Regex RegexSearch = new Regex("Audi A4", RegexOptions.Compiled); 

    static void Main(string[] args) 
    {    
     // warm up the JIT compiler 
     FoundWithRegexIsMatch(); 
     FoundWithStringContains(); 
     FoundWithStringIndexOf(); 
     // done warming up 

     int iterations = 100; 
     var sw = new Stopwatch(); 

     sw.Restart(); 
     for (int i = 0; i < iterations; i++) 
     { 
      FoundWithRegexIsMatch(); 
     } 
     sw.Stop(); 
     Console.WriteLine("Regex.IsMatch: " + sw.ElapsedMilliseconds + " ms"); 


     sw.Restart(); 
     for (int i = 0; i < iterations; i++) 
     { 
      FoundWithStringIndexOf(); 
     } 
     sw.Stop(); 
     Console.WriteLine("String.IndexOf: " + sw.ElapsedMilliseconds + " ms"); 


     sw.Restart(); 
     for (int i = 0; i < iterations; i++) 
     { 
      FoundWithStringContains(); 
     } 
     sw.Stop(); 
     Console.WriteLine("String.Contains: " + sw.ElapsedMilliseconds + " ms"); 
    } 

    private static bool FoundWithStringContains() 
    { 
     return Resource2.csv.Contains("Audi A4"); 
    } 

    private static bool FoundWithStringIndexOf() 
    { 
     return Resource2.csv.IndexOf("Audi A4") >= 0; 
    } 

    private static bool FoundWithRegexIsMatch() 
    { 
     return RegexSearch.IsMatch(Resource2.csv); 
    } 

我測試對一個CSV文件,我有一個約1.2 MB。而這裏的結果:

  • Regex.IsMatch:172毫秒
  • String.IndexOf:172毫秒
  • String.Contains:185毫秒

你的確是正確的。當你在正則表達式中沒有做任何事情時,正則表達式比String.Contains操作快一點。然而,我發現有一個tiny bit of overhead in String.Contains和呼叫String.IndexOf是更快,實際上匹配Regex.IsMatch毫秒的時間。

這些相同的時間是可疑的。我想知道在編譯期間是否確實不需要通過Regex引擎(因爲在上面使用的模式中沒有針對特定於正則表達式的指令)。相反,它可能會被簡化,以致Regex.IsMatchIndexOf一樣,會從kernel32.dll對FindNLSString進行相同的調用。這只是一個猜測。

+0

有趣。我沒有用最近的一批測試來實際測試IndexOf--它不是這些類型操作的(語義)直觀方法。 –

+0

@ChrisPhillips - 我同意。看起來'Contains'主要是使用'StringComparison.Ordinal'。您可以將其添加到我編寫的'IndexOf'示例並獲得相似的時間。 –

+0

@ChrisPhillips - 我做了一些更多的測試,發現更多情況下'Regex.IsMatch'比甚至是對'String.IndexOf'的調用要快。所以我不認爲我的理論可能是正確的。 –

相關問題