7

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

为了查找字节数组,我使用了此处描述的方法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;
        }
    }

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

4

2 回答 2

10

基本上,查找字节数组中字节序列的下一个匹配项所花费的时间是查找字符串中字符序列的下一个匹配项的时间的三倍。我的问题是——为什么?

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

你的算法很好——这是标准的“朴素”搜索,尽管凯文声称,朴素算法在实践中几乎总是在典型数据上最快的。在现代硬件上,通过数组查找字节的速度非常快。这取决于您的问题的大小;如果您正在寻找人类基因组中间的长 DNA 串,那么 Boyer-Moore 完全值得花费预处理。如果您正在寻找0xDEADBEEF一个 20 KB 的文件,那么如果它被有效地实现,那么您将无法击败朴素算法。

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

于 2013-05-31T16:41:22.347 回答
4

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

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

编辑:所以来救援! 这是用于字节数组的 Boyer-Moore 的简单 C# 实现

使用计时编号进行编辑: Eric 的评论让我非常感兴趣,因为我一直听说 .Net 字符串搜索使用 Boyer-Moore。我真的很感谢一个真正知道告诉我的人。仔细想了想,就说得通了。我决定对 Boyer-Moore 与 Naive 字节搜索进行计时,发现 Eric 对于小针和小草垛绝对正确,天真的搜索速度快了 13 倍以上。令我惊讶的是,“收支平衡”点远低于我的预期。Boyer-Moore 的图案尺寸(或我的计时中的针头尺寸)显着提高,因此您正在寻找的图案越大,它搜索的速度就越快。对于 8 字节的针 Naive 搜索与 Boyer-Moore 并列搜索通过 500-600 字节的大海捞针。对于更大的干草堆,Boyer-Moore 会明显更好,尤其是使用更大的针头。对于 20KB 的 haystack 和 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
于 2013-05-31T13:10:44.000 回答