2013-05-31 53 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