.net core 为什么我的ReadOnlySpan<;char>;比我的绳子慢吗?

.net core 为什么我的ReadOnlySpan<;char>;比我的绳子慢吗?,.net-core,.net Core,我正在玩ReadOnlySpan,我想自己看看,这比使用字符串快得多,但是。。。到目前为止,还没有。我知道我的代码可能出错了,但我找不到 static int CountCharacterWithoutSpan(string originalString, string sequence) { int count = 0; for (int i = 0, length = originalString.Length - sequence.Length; i < lengt

我正在玩
ReadOnlySpan
,我想自己看看,这比使用字符串快得多,但是。。。到目前为止,还没有。我知道我的代码可能出错了,但我找不到

static int CountCharacterWithoutSpan(string originalString, string sequence)
{
    int count = 0;

    for (int i = 0, length = originalString.Length - sequence.Length; i < length; ++i)
    {
        if (originalString.Substring(i, sequence.Length).Equals(sequence))
        {
            count++;
        }
    }

    return count;
}

static int CountCharacterWithSpan(ReadOnlySpan<char> originalString, string sequence)
{
    int count = 0;

    for (int i = 0, length = originalString.Length - sequence.Length; i < length; ++i)
    {
        if (originalString.Slice(i, sequence.Length).SequenceEqual(sequence))
        {
            count++;
        }
    }

    return count;
}
static int countcharacterwithout span(字符串原始字符串,字符串序列)
{
整数计数=0;
for(int i=0,length=originalString.length-sequence.length;i
所以基本上,这段代码的目标是能够在另一个字符串中找到一个字符串。两者之间的区别在于我使用
Slice
而不是
Substring
SequenceEqual
而不是
Equals
。但是,当我使用秒表运行和监视此代码时,
CountCharacterWithSpan
总是比
CountCharacterWithoutSpan
多花2到3倍的时间(字符串测试大约为80K个字符)


我认为问题来自于
SequenceEquals
,但这是我发现的唯一比较切片
ReadOnlySpan
和常规字符串的方法(
Equals
不起作用,
=
更快,但比较引用,因此结果不正确)

与你在问题中所说的相反,基于跨度的版本实际上比不基于跨度的版本快得多

根据morten mertner在评论中的建议,我对您的第二种方法做了一点修改:

public static int CountCharacterWithSpan(
ReadOnlySpan原始字符串,ReadOnlySpan序列)
{
整数计数=0;
for(int i=0,length=originalString.length-sequence.length;i
但正如我们将看到的,这没有什么区别。它的速度大约与原始的基于跨度的速度一样快,而且两者都比不基于跨度的速度快得多

下面是BenchmarkDotNet针对这三个方面的报告,使用80K字符的原始字符串和运行在.NET Core 2.2上的20字符的序列,每个都有三个变体。在“随机”变体中,
序列
只是随机文本,因此可以很早检测到不匹配。在“匹配”变体中,
序列
实际上是文本中某个地方存在的子字符串,但输入仍然是随机的,因此大多数搜索都会很快终止,但速度会很慢。在“匹配所有”的情况下,
originalString
sequence
都是相同的字符,这意味着每次比较都会成功,这意味着尽可能多的比较工作。(需要反复比较每个字符。)

以下是将
序列更改为200个字符的结果:

|                      Method |       Mean |     Error |    StdDev |
|---------------------------- |-----------:|----------:|----------:|
|   OriginalWithoutSpanRandom | 2,432.2 us | 35.777 us | 31.715 us |
|    OriginalWithoutSpanMatch | 2,476.1 us | 42.809 us | 35.747 us |
| OriginalWithoutSpanMatchAll | 2,815.6 us | 22.508 us | 19.953 us |
|      OriginalWithSpanRandom |   190.2 us |  1.531 us |  1.432 us |
|       OriginalWithSpanMatch |   189.8 us |  1.937 us |  1.717 us |
|    OriginalWithSpanMatchAll |   602.3 us |  4.662 us |  4.361 us |
|      ModifiedWithSpanRandom |   190.1 us |  2.200 us |  2.058 us |
|       ModifiedWithSpanMatch |   191.1 us |  2.860 us |  2.675 us |
|    ModifiedWithSpanMatchAll |   599.9 us |  3.696 us |  3.457 us |
|                      Method |        Mean |      Error |     StdDev |
|---------------------------- |------------:|-----------:|-----------:|
|   OriginalWithoutSpanRandom | 16,819.9 us | 310.576 us | 290.513 us |
|    OriginalWithoutSpanMatch | 17,148.8 us | 231.140 us | 216.209 us |
| OriginalWithoutSpanMatchAll | 21,817.9 us | 246.378 us | 218.408 us |
|      OriginalWithSpanRandom |    184.2 us |   1.633 us |   1.528 us |
|       OriginalWithSpanMatch |    185.3 us |   1.440 us |   1.347 us |
|    OriginalWithSpanMatchAll |  4,649.7 us |  22.810 us |  20.221 us |
|      ModifiedWithSpanRandom |    185.2 us |   1.198 us |   1.120 us |
|       ModifiedWithSpanMatch |    186.7 us |   2.158 us |   2.019 us |
|    ModifiedWithSpanMatchAll |  4,651.1 us |  25.013 us |  22.173 us |
下面是将
序列更改为2000个字符时的情况:

|                      Method |       Mean |     Error |    StdDev |
|---------------------------- |-----------:|----------:|----------:|
|   OriginalWithoutSpanRandom | 2,432.2 us | 35.777 us | 31.715 us |
|    OriginalWithoutSpanMatch | 2,476.1 us | 42.809 us | 35.747 us |
| OriginalWithoutSpanMatchAll | 2,815.6 us | 22.508 us | 19.953 us |
|      OriginalWithSpanRandom |   190.2 us |  1.531 us |  1.432 us |
|       OriginalWithSpanMatch |   189.8 us |  1.937 us |  1.717 us |
|    OriginalWithSpanMatchAll |   602.3 us |  4.662 us |  4.361 us |
|      ModifiedWithSpanRandom |   190.1 us |  2.200 us |  2.058 us |
|       ModifiedWithSpanMatch |   191.1 us |  2.860 us |  2.675 us |
|    ModifiedWithSpanMatchAll |   599.9 us |  3.696 us |  3.457 us |
|                      Method |        Mean |      Error |     StdDev |
|---------------------------- |------------:|-----------:|-----------:|
|   OriginalWithoutSpanRandom | 16,819.9 us | 310.576 us | 290.513 us |
|    OriginalWithoutSpanMatch | 17,148.8 us | 231.140 us | 216.209 us |
| OriginalWithoutSpanMatchAll | 21,817.9 us | 246.378 us | 218.408 us |
|      OriginalWithSpanRandom |    184.2 us |   1.633 us |   1.528 us |
|       OriginalWithSpanMatch |    185.3 us |   1.440 us |   1.347 us |
|    OriginalWithSpanMatchAll |  4,649.7 us |  22.810 us |  20.221 us |
|      ModifiedWithSpanRandom |    185.2 us |   1.198 us |   1.120 us |
|       ModifiedWithSpanMatch |    186.7 us |   2.158 us |   2.019 us |
|    ModifiedWithSpanMatchAll |  4,651.1 us |  25.013 us |  22.173 us |
正如您所看到的,我无法重现您描述的结果,即“
CountCharacterWithSpan
总是比
CountCharacterWithoutSpan
多花2到3倍的时间”。在这些测试中,
CountCharacterWithoutSpan
始终比基于
ReadOnlySpan
的版本慢很多。(但两者之间的差异太小,无法衡量。)

对于这两种基于跨度的方法,每次比较所做的工作量都非常大:您可以看到大多数字符串比较可以在一两个字符后退出的测试与必须比较每个单个字符的测试之间存在很大的差异。(不过,
Random
Match
的例子之间并没有什么明显的区别——似乎所有的比较都提前退出,而一个比较提前退出的成本差异很小。这并不奇怪,因为我们基本上看到80000个比较中有一个比较昂贵,其余的比较昂贵。)eap

这里非常清楚的一点是,非基于span的版本非常昂贵。对
子字符串的调用杀死了它。在大多数比较几乎立即失败的测试中,情况尤其糟糕:您分配了
原始字符串的某个子字符串的2000个字符副本,然后只查看ha充满了人物

请注意,在我们能够提前退出的情况下,基于span的版本的性能几乎与
序列的长度无关,在所有情况下大约为190us。这就是我们能够在很早的时候确定没有匹配的情况下所希望的,不管
序列有多长是的,但在非基于span的版本中,
序列的长度
即使在这些情况下也非常重要


您在测试中进行了多少次测量?我想知道您是否只是测量一次运行,在这种情况下,您并不是真正测量代码运行所需的时间:您主要是测量JIT编译器编译代码所需的时间。

我知道这是过时的,但可能的原因是您需要创建跨度对于循环外的两个字符串。