在byte[]和string中查找byte[]的速度,为什么后者更快

本文关键字:byte 为什么 查找 string 速度 | 更新日期: 2023-09-27 18:16:39

我有一个任务,我需要在文件中查找序列。在做测试应用程序时,我已经将文件读取为字符串(file . readalltext)并使用字符串。IndexOf查找一个序列。当我尝试用字节实现相同的算法(将文件读取为字节数组并在字节数组中查找字节数组)时,我注意到在byte[]中查找byte[]的速度大约是在string中查找string的3倍。我确保彻底检查了它,完全相同的代码,一个使用byte[],另一个使用string,需要3倍的时间来执行-例如,16秒字节vs ~5秒字符串。

对于查找字节数组,我使用这里描述的byte[]数组模式搜索方法。为了查找字符串,我使用了字符串类的内置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;
    }

基本上,在bytes数组中查找下一个匹配的字节序列需要的时间是在string中查找下一个匹配的字符序列的三倍。我的问题是——为什么?

有没有人知道。net如何处理查找字符串中的字符,它做了什么样的优化,它使用什么算法?有没有比我现在用的更快的算法?也许有人知道我哪里做错了所以花了太多时间?我真的不明白为什么在string中查找string可以比在byte[]中查找byte[]快3倍…

UPDATE:我已经按照建议尝试了不安全的算法。内容如下:

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;
        }
    }

现在,它的执行时间与安全的完全相同。我的问题是,知道为什么吗?难道它不应该更快吗,因为它是不安全的,并且与指针操作,与安全相比?

在byte[]和string中查找byte[]的速度,为什么后者更快

基本上,在bytes数组中查找下一个匹配的字节序列需要的时间是在string中查找下一个匹配的字符序列的三倍。我的问题是——为什么?

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

你的算法很好——这是标准的"朴素"搜索,尽管Kevin声称,朴素算法在实践中几乎总是在典型数据上最快的。在现代硬件上,遍历数组查找一个字节非常快。这取决于你的问题有多大;如果你想在人类基因组中寻找一长串DNA,那么博伊尔-摩尔完全值得你花时间进行预处理。如果你在一个20kb的文件中寻找0xDEADBEEF,那么如果它被有效地实现,你就不会打败朴素算法。

为了获得最大的速度,你应该在这里做的是关闭安全系统并使用不安全的指针算术来编写代码。

您的字节搜索算法效率极低!

与所有其他字符串搜索进行比较的基线算法是Boyer-Moore。我敢打赌。net字符串搜索使用它或它的变体。也有其他的,但是实现Boyer-Moore for bytes会给你更好的性能。

编辑:SO to rescue!下面是一个简单的用于字节数组的Boyer-Moore的c#实现

编辑时序数字:Eric的评论让我非常感兴趣,因为我一直听说。net字符串搜索使用Boyer-Moore。我真的很感激有个真正知道真相的人告诉我事实并非如此。仔细想想就明白了。我决定对Boyer-Moore和Naive字节搜索进行计时,并发现Eric绝对是正确的,对于小针头和小草堆,Naive搜索要快13倍以上。但令我惊讶的是,"收支平衡"点比我预期的要低得多。Boyer-Moore在模式大小(或我的计时针大小)上有显著提高,所以你寻找的模式越大,它搜索的速度就越快。对于8字节的针头,Naive搜索与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