2009-01-09 40 views
8

這只是一個問題,以滿足我的好奇心。 但對我來說這很有趣。爲什麼緩存的Regexp超越編譯好的版本?

我寫了這個簡單的基準。它以幾千次的隨機順序調用Regexp執行的3個變體:

基本上,我使用相同的模式,但以不同的方式。

  1. 您的普通方式沒有任何RegexOptions。從.NET 2.0開始,這些不會被緩存。但應該「緩存」,因爲它保存在一個非常全球的範圍內,而不是重置。

  2. 隨着RegexOptions.Compiled

  3. 隨着到靜態Regex.Match(pattern, input)調用它不會在.NET 2.0

這裏獲取緩存是代碼:

static List<string> Strings = new List<string>();   
static string pattern = ".*_([0-9]+)\\.([^\\.])$"; 

static Regex Rex = new Regex(pattern); 
static Regex RexCompiled = new Regex(pattern, RegexOptions.Compiled); 

static Random Rand = new Random(123); 

static Stopwatch S1 = new Stopwatch(); 
static Stopwatch S2 = new Stopwatch(); 
static Stopwatch S3 = new Stopwatch(); 

static void Main() 
{ 
    int k = 0; 
    int c = 0; 
    int c1 = 0; 
    int c2 = 0; 
    int c3 = 0; 

    for (int i = 0; i < 50; i++) 
    { 
    Strings.Add("file_" + Rand.Next().ToString() + ".ext"); 
    } 
    int m = 10000; 
    for (int j = 0; j < m; j++) 
    { 
    c = Rand.Next(1, 4); 

    if (c == 1) 
    { 
     c1++; 
     k = 0; 
     S1.Start(); 
     foreach (var item in Strings) 
     { 
     var m1 = Rex.Match(item); 
     if (m1.Success) { k++; }; 
     } 
     S1.Stop(); 
    } 
    else if (c == 2) 
    { 
     c2++; 
     k = 0; 
     S2.Start(); 
     foreach (var item in Strings) 
     { 
     var m2 = RexCompiled.Match(item); 
     if (m2.Success) { k++; }; 
     } 
     S2.Stop(); 
    } 
    else if (c == 3) 
    { 
     c3++; 
     k = 0; 
     S3.Start(); 
     foreach (var item in Strings) 
     { 
     var m3 = Regex.Match(item, pattern); 
     if (m3.Success) { k++; }; 
     } 
     S3.Stop(); 
    } 
    } 

    Console.WriteLine("c: {0}", c1); 
    Console.WriteLine("Total milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString()); 
    Console.WriteLine("Adjusted milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString()); 

    Console.WriteLine("c: {0}", c2); 
    Console.WriteLine("Total milliseconds: " + (S2.Elapsed.TotalMilliseconds).ToString()); 
    Console.WriteLine("Adjusted milliseconds: " + (S2.Elapsed.TotalMilliseconds*((float)c2/(float)c1)).ToString()); 

    Console.WriteLine("c: {0}", c3); 
    Console.WriteLine("Total milliseconds: " + (S3.Elapsed.TotalMilliseconds).ToString()); 
    Console.WriteLine("Adjusted milliseconds: " + (S3.Elapsed.TotalMilliseconds*((float)c3/(float)c1)).ToString()); 
} 

每次我叫它結果如下:

 
    Not compiled and not automatically cached: 
    Total milliseconds: 6185,2704 
    Adjusted milliseconds: 6185,2704 

    Compiled and not automatically cached: 
    Total milliseconds: 2562,2519 
    Adjusted milliseconds: 2551,56949184038 

    Not compiled and automatically cached: 
    Total milliseconds: 2378,823 
    Adjusted milliseconds: 2336,3187176891 

所以你有它。不多,但約7-8%的差異。

這不是唯一的祕密。我無法解釋爲什麼第一種方法會慢得多,因爲它不會被重新評估,而是被保存在一個全局靜態變量中。

順便說一句,這是在.net 3.5和Mono 2.2上表現完全一樣。在Windows上。

那麼,任何想法,爲什麼編譯的變體甚至會落後?

EDIT1:

 
    Not compiled and not automatically cached: 
    Total milliseconds: 6456,5711 
    Adjusted milliseconds: 6456,5711 

    Compiled and not automatically cached: 
    Total milliseconds: 2668,9028 
    Adjusted milliseconds: 2657,77574842168 

    Not compiled and automatically cached: 
    Total milliseconds: 6637,5472 
    Adjusted milliseconds: 6518,94897724836 

還幾乎廢止所有的其他問題還有:

固定碼結果現在這個樣子了。

感謝您的答案。

回答

4

在Regex.Match版本中,您正在尋找模式中的輸入。嘗試交換周圍的參數。

var m3 = Regex.Match(pattern, item); // Wrong 
var m3 = Regex.Match(item, pattern); // Correct 
3

I noticed類似的行爲。我也想知道爲什麼編譯版本會變慢,但是注意到在一定數量的調用之上,編譯版本更快。所以我挖了一點Reflector,我注意到對於一個編譯的正則表達式,仍然有一點設置是在第一次調用時執行的(具體來說,創建一個合適的對象實例)。

在我的測試中,我發現如果我將構造函數和最初的throw-away調用移動到定時器啓動之外的正則表達式中,編譯的正則表達式無論我運行了多少次迭代都會贏。


順便說一句,使用靜態Regex方法時,該框架是做緩存使用靜態Regex方法時唯一需要的優化。這是因爲每次調用靜態方法Regex時都會創建一個新的Regex對象。在Regex類的構造函數中,它必須解析模式。緩存允許靜態方法的後續調用重用從第一次調用解析的RegexTree,從而避免解析步驟。

當您在單個Regex對象上使用實例方法時,這不是問題。解析仍然只執行一次(當您創建對象時)。另外,你可以避免運行構造函數中的所有其他代碼,以及堆分配(和後續的垃圾回收)。

馬丁布朗noticed你扭轉了你的靜態Regex調用的參數(好趕,馬丁)。我認爲你會發現,如果你解決這個問題,實例(未編譯)的正則表達式將每次都打敗靜態調用。你也應該發現,鑑於我上面的發現,編譯的實例也會擊敗未編譯的實例。

但是:在對所創建的每個正則表達式盲目應用該選項之前,您應該在編譯的正則表達式上真正閱讀Jeff Atwood's post

+0

謝謝你的解釋。 在我的情況下,最初的步驟似乎不會導致很多成本(請參閱新結果)。 我在發佈之前閱讀了Jeff Atwood的帖子。所以我知道這些缺點。在我的情況下,編譯選項會有所幫助,雖然在標準用例中沒有那麼多。 – user51710 2009-01-09 15:15:52

+0

** Jeff Atwood的帖子已經移到:[編譯或不編譯*(2005年3月3日)*](http://blog.codinghorror.com/to-compile-or-not-to-compile/) – DavidRR 2014-04-10 17:39:43

0

如果您經常使用相同的模式匹配相同的字符串,這可以解釋爲什麼緩存版本比編譯版本稍快。

0

這是來自文檔;

https://msdn.microsoft.com/en-us/library/gg578045(v=vs.110).aspx

靜態正則表達式方法被調用並且常規 表達式不能在高速緩存中找到,正則表達式引擎 正則表達式轉換成一組操作碼和他們在緩存中存儲 。然後它將這些操作代碼轉換爲MSIL,以便JIT編譯器可以執行它們。 解釋常規 表達式以較慢的執行時間爲代價減少啓動時間。 正因爲如此,它們是當正則表達式是在一個小的數目的方法的使用最好 用於調用,或者如果 調用正則表達式的方法的確切數目是未知的,但預期是 小。隨着方法調用次數的增加,啓動時間縮短後的性能增益 超過了執行速度較慢的執行速度。

相反解釋的正則表達式,編譯的正則表達式 增加啓動時間,但執行個人 模式匹配方法快。因此,編譯正則表達式導致的性能優勢 與調用的正則表達式方法的數量成比例地增加了 。


總之,我們建議您使用解釋的正則表達式,當你調用一個特定 正則表達式的正則表達式的方法相對較少。

,當你調用定期 表達方式與特定的正則表達式相對 頻繁您應該使用編譯的正則表達式


如何檢測?

確切閾處的 解釋的正則表達式的更慢的執行速度大於從它們的還原 啓動時間增益,或在其中的 編譯的正則表達式的慢啓動時間超過從它們更快 執行增益閾值速度,很難確定。它取決於多種因素,包括正則表達式的複雜性和它處理的特定數據。 要確定編譯的正則表達式是否爲您的 特定應用場景提供最佳性能,可以使用秒錶類 比較其執行時間


編譯的正則表達式:

我們建議您編譯正則表達式的組件 以下幾種情況:

  1. 如果你是誰想要 組件開發創建一個可重複使用的正則表達式庫。
  2. 如果您期望 您的正則表達式的模式匹配方法被稱爲 不確定的次數 - 任何地方從一次或兩次到 成千上萬或幾萬次。與編譯的或 解釋的正則表達式不同,編譯爲 以分離程序集的正則表達式提供的性能是一致的,無論方法調用的數量是多少 。
相關問題