在byte []中查找byte []和在string中查找字符串的速度 - 为什么后者更快?

时间:2013-05-31 13:01:50

标签: c# string search byte bytearray

我有一个任务,我需要在文件中查找序列。在进行测试应用程序时,我将文件读取为字符串(File.ReadAllText)并使用string.IndexOf来查找序列。当我尝试用字节实现相同的算法(将文件作为字节数组读取并在字节数组中查找字节数组)时,我注意到在byte []中查找byte []的速度大约是在字符串中查找字符串的3倍。我确保彻底检查它,完全相同的代码,一个使用byte []和其他使用字符串,执行的次数是3倍 - 比如16s for byte vs~5s for string。

为了查找字节数组,我使用了这里描述的方法byte[] array pattern search。为了查找字符串,我使用了字符串类的内置IndexOf函数。这是我试过的byte []的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如何处理在字符串中查找字符,它做什么样的优化,它使用什么算法?是否有比我在这里使用的算法更快的算法?也许有人知道我在这里做错了所以它需要的时间比它应该多吗?我真的不明白如何在字符串中查找字符串的速度是在byte []中查找byte []的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;
        }
    }

现在,执行安全的时间与完全相同。我的问题又来了 - 任何想法为什么?与安全相比,它不应该更快,因为它不安全并且使用指针操作吗?

2 个答案:

答案 0 :(得分:11)

  

基本上,只要在字符串中查找字符序列的下一个匹配,查找字节序列的下一个匹配就会花费三倍的时间。我的问题是 - 为什么?

因为字符串搜索算法已经过大量优化;它是用严格的非托管代码编写的,不需要花时间检查数组边界。如果你以同样的方式优化你的字节搜索算法,它会同样快;字符串搜索算法使用您正在使用的相同的天真算法。

你的算法很好 - 这是标准的“幼稚”搜索,尽管凯文声称,但天真的算法在典型数据中几乎总是最快的。在现代硬件上浏览一个寻找字节的数组非常快。这取决于你的问题的大小;如果你正在寻找人类基因组中间的长DNA链,那么Boyer-Moore完全值得花费预处理。如果您在20 KB的文件中查找0xDEADBEEF,那么如果有效实施,您就不会打败天真的算法。

为了获得最大速度,您应该在关闭安全系统并使用不安全的指针算法编写代码。

答案 1 :(得分:3)

你的字节搜索算法非常低效!

与所有其他字符串搜索进行比较的基线算法是Boyer-Moore。我敢打赌.NET字符串搜索使用它或它的变体。还有others但是为字节实现Boyer-Moore会给你带来更好的性能。

编辑:SO救援! Here is a simple C# implementation of Boyer-Moore for byte arrays

使用时间码编辑 Eric的评论让我非常感兴趣,因为我一直听说.Net字符串搜索使用的是Boyer-Moore。我非常感谢真正告诉我的人。在思考之后它就变得非常有意义了。我决定对Boyer-Moore vs Naive字节搜索进行计时,发现Eric对于小针和小干草堆来说绝对正确,天真的搜索速度快了13倍。令我感到惊讶的是,“收支平衡”点远低于我的预期。 Boyer-Moore随着图案尺寸(或我的时间中的针尺寸)显着改善,因此您寻找的图案越大,搜索的速度就越快。对于一个8字节的针头Naive搜索与Boyer-Moore一起搜索了一个500-600字节的干草堆。对于较大的草垛,Boyer-Moore会变得更好,特别是使用更大的针头。对于一个20KB的干草堆和64字节的针,Boyer-Moore要快10倍。

任何感兴趣的人都可以获得完整的时间数字。

所有测试都使用上面链接的简单Boyer-Moore和他发布的1M搜索迭代的Op的天真字节搜索算法。

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