2013-05-31 93 views
7

我有一個任務,我需要查找文件中的序列。在做測試應用程序時,我已經將文件讀取爲字符串(File.ReadAllText)並使用string.IndexOf來查找序列。當我試圖用字節實現相同的算法(以字節數組讀取文件並在字節數組中查找字節數組)時,我注意到在byte []中查找byte []的速度大約是在字符串中查找字符串的3倍。我一定要徹底檢查它,並且完全相同的代碼,使用byte []和其他使用字符串的代碼需要執行3倍的數量 - 例如,字節爲16秒,字符串爲〜5秒。在字節[]中查找字節[]的速度和在字符串中的字符串 - 爲什麼後者更快?

爲了查找字節數組,我使用了這裏描述的方法byte[] array pattern search。爲了查找字符串,我使用了string類的內置IndexOf函數。下面是的IndexOf的實現爲字節[]我嘗試之一:

public int IndexOf(byte[] source, byte[] pattern, int startpos = 0) 
    { 
     int search_limit = source.Length - pattern.Length; 
     for (int i = startpos; i < search_limit; i++) 
     { 
      if (source[i] == pattern[0]) 
      { 
       bool found = true; 
       for (int j = 1; j < pattern.Length; j++) 
       { 
        if (source[i + j] != pattern[j]) 
        { 
         found = false; 
         break; 
        } 
       } 
       if (found) 
        return i; 
      } 
     } 
     return -1; 
    } 

基本上,查找下一個匹配對以字節陣列的字節序列有三個時間只要查找用於在字符的序列下一個匹配串。我的問題是 - 爲什麼?

有誰知道.net處理查找字符串中的字符,它做了什麼樣的優化,它使用什麼算法?有沒有比我在這裏使用的算法更快的算法?也許有人有一個想法,我在這裏做錯了,所以它需要更多的時間比它應該?我真的不明白如何在字符串中查找字符串可以是在字節[]中查找字節[]的3倍...

更新:我已經嘗試了建議的不安全算法。它如下:

public static unsafe long IndexOfFast(byte[] Haystack, byte[] Needle, long startpos = 0) 
    { 
     long i = startpos; 
     fixed (byte* H = Haystack) fixed (byte* N = Needle) 
     { 
      for (byte* hNext = H + startpos, hEnd = H + Haystack.LongLength; hNext < hEnd; i++, hNext++) 
      { 

        bool Found = true; 
        for (byte* hInc = hNext, nInc = N, nEnd = N + Needle.LongLength; Found && nInc < nEnd; Found = *nInc == *hInc, nInc++, hInc++) ; 
        if (Found) return i; 

      } 
      return -1; 
     } 
    } 
} 

奇怪的是,它實際上證明是慢一倍!我改成了加我個人的調整(檢查第一個字母試圖通過針頭重複之前),現在看起來是這樣的:

public static unsafe long IndexOfFast(byte[] Haystack, byte[] Needle, long startpos = 0) 
    { 
     long i = startpos; 
     fixed (byte* H = Haystack) fixed (byte* N = Needle) 
     { 
      for (byte* hNext = H + startpos, hEnd = H + Haystack.LongLength; hNext < hEnd; i++, hNext++) 
      { 
       if (*hNext == *N) 
       { 
        bool Found = true; 
        for (byte* hInc = hNext+1, nInc = N+1, nEnd = N + Needle.LongLength; Found && nInc < nEnd; Found = *nInc == *hInc, nInc++, hInc++) ; 
        if (Found) return i; 
       } 
      } 
      return -1; 
     } 
    } 

現在,它需要完全相同的時間來執行的安全可靠。我的問題再次 - 任何想法爲什麼?難道它不是更快,因爲它是不安全的,並與指針相比,當與安全相比?

+0

我會爲字節數組實現[this algorithm](http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm)並再次測試。 – I4V

+1

那麼,大多數字符串比較函數和'IndexOf'歸結爲內部CLR調用,通常是'InternalFindNLSStringEx'或'InternalCompareString'。本地CLR實現可能會更快。 – vcsjones

+0

好的,現在你已經有了一個指針算術解決方案,如果你想使它更快,你將不得不開始考慮在那裏實際上正在做什麼機器操作。例如:更快的方法是:將一個字節從32位字中移出四次,或者製作四個掩碼,一個字節位於四個位置,將字節數組視爲int數組,並檢查每個int以查看與掩碼進行「與」操作時是否匹配任何掩碼?後者可能會快幾個週期。這是人們在優化此算法時所做的一些事情。 –

回答

3

您的字節搜索算法效率極低!

所有其他字符串搜索相比較的基準算法是Boyer-Moore。我敢打賭,.NET字符串搜索使用它或它的變體。也有others也可以實現Boyer-Moore字節會給你更好的性能。

編輯:SO來拯救!Here is a simple C# implementation of Boyer-Moore for byte arrays

編輯帶定時數字: Eric的評論讓我很感興趣,因爲我一直聽說的淨字符串搜索使用博耶 - 穆爾。我真的很感激某個真正知道告訴我的人。在思考它之後,它非常有意義。我決定在Boyer-Moore vs Naive字節搜索上做計時,並發現Eric對於一個小針和小乾草堆來說絕對是正確的,這個天真的搜索速度比以前快了13倍。然而,我驚訝的是「盈虧平衡點」遠低於我的預期。 Boyer-Moore的圖案尺寸(或我的時間尺寸)顯着提高,因此您尋找的圖案越大,搜索速度越快。對於一個8字節的針樸素搜索與Boyer-Moore通過500-600字節的乾草堆進行綁定搜索。對於更大的草垛,Boyer-Moore特別是用更大的針頭變得更好。對於20KB的乾草堆和64字節的指針,Boyer-Moore速度提高了10倍。

對於任何有興趣的人來說,完整的計時數字如下。

所有的測試都使用了上面簡單的Boyer-Moore鏈接,以及Op公司的樸素字節搜索算法,他發佈了1M搜索迭代。

1000000 iterations, haystack size = 20 bytes, needle size = 8 bytes 
20ms total : Naive Search 
268ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 600 bytes, needle size = 8 bytes 
608ms total : Naive Search 
582ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 2000 bytes, needle size = 8 bytes 
2011ms total : Naive Search 
1339ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 2000 bytes, needle size = 32 bytes 
1865ms total : Naive Search 
563ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 2000 bytes, needle size = 64 bytes 
1883ms total : Naive Search 
466ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 20000 bytes, needle size = 8 bytes 
18899ms total : Naive Search 
10753ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 20000 bytes, needle size = 32 bytes 
18639ms total : Naive Search 
3114ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 20000 bytes, needle size = 64 bytes 
18866ms total : Naive Search 
1807ms total : Boyer-Moore Search 
+0

極度?在我提供的鏈接中,它是最快的。你能詳細說明我的算法有什麼問題嗎? – Istrebitel

+0

您不需要檢查模式開始的每個字節。通過對模式字節進行一些預處理,您可以在搜索數組中跳過前進。 Boyer-Moore提高了效率,特別是因爲您可以跳過更多的搜索陣列。 – Kevin

+0

嗯,我現在看到。 Boyer-Moore是一個古老的算法,不應該有一個用於byte []的c#的衆所周知的實現?我的意思是我可以嘗試寫下自己的想法,但不想發明輪子。 – Istrebitel

11

基本上,查找下一個匹配對以字節陣列的字節序列有三個時間只要查找下一個匹配串中字符的序列。我的問題是 - 爲什麼?

因爲字符串搜索算法已經大大優化;它是用緊密的非託管代碼編寫的,它不花時間檢查數組邊界。如果你想以同樣的方式優化你的字節搜索算法,它會一樣快;字符串搜索算法使用您正在使用的相同樸素算法。

你的算法很好 - 這是標準的「天真」搜索,儘管如此,凱文的說法是,天真算法在實踐中幾乎總是最快的典型數據。通過陣列尋找一個字節是令人難以置信的快速在現代硬件上。這取決於你的問題的大小;如果你想在人類基因組中找到一條長DNA鏈,那麼Boyer-Moore完全值得犧牲預處理。如果你正在尋找一個20KB的文件中的0xDEADBEEF,那麼如果它被有效地實現,你就不會擊敗天真的算法。

對於你應該在這裏做的最大速度是關閉安全系統和編寫使用不安全的指針運算的代碼。

+0

以爲你會對我添加的時間數字感興趣。它肯定證明,對於通過少量數據搜索的「典型數據」,樸素搜索更好。 – Kevin