【问题标题】:How to swap 2 Span<char> without using stackalloc如何在不使用 stackalloc 的情况下交换 2 个 Span<char>
【发布时间】:2022-11-11 04:08:35
【问题描述】:

考虑一个可写字符串(Span,仅作为测试用例!) 像这样: "你好,世界,你好吗?"

我有两个跨度。

跨度1:“你好”

span2:“世界”

我找不到真正交换这两个跨度的好方法.. 所以我最后有:

“世界你好,你好吗?”

如果 some1 能接受这个,我将非常感激:-)

此致。

编辑: 这是我已经做过的,但遗憾的是它使用了 stackalloc,我很乐意允许交换内存。 :-)

 public static ReadOnlySpan<char> Swap(this ReadOnlySpan<char> main, ReadOnlySpan<char> a, ReadOnlySpan<char> b)
    {
        var needed = AsSpan(main.Slice(main.IndexOf(a), (a.Length + b.Length)));
        
        Span<char> aBuffer = stackalloc char[a.Length];
        AsSpan(a).CopyTo(aBuffer);
        Span<char> bBuffer = stackalloc char[b.Length];
        AsSpan(b).CopyTo(bBuffer);
        bBuffer.CopyTo(needed, out int writtenCount);
        aBuffer.CopyTo(needed[(writtenCount)..]);
        return needed;
    }

【问题讨论】:

  • 你能把它写成代码,而不是你想要做什么的描述吗?
  • 跨度3 = 跨度2,跨度2 = 跨度1,跨度1 = 跨度3。

标签: c# performance


【解决方案1】:

字符串是不可变的(只读)。如果您使用的是字符串,您将始终需要一个 stackalloc 来获取该字符串的可写副本:

var str = "hello world, how are you?"
var span = stackalloc char[str.Length];
str.AsSpan().CopyTo(span);

假设您已经将消息作为跨度,它可以在不使用另一个 stackalloc 的情况下交换 span 内的数据。在此示例中,两个跨度都是 5 个字符长。您可以一个一个地交换单个字符:

var part1 = span.Slice(0, 5);
var part2 = span.Slice(6, 5);

foreach (var i = 0; i < 5; i++)
{
    var temp = part1[i];
    part1[i] = part2[i];
    part2[i] = temp;
}

当然,这提出了一些问题:

  • 如果交换的两个跨度长度不同,会发生什么情况?
  • 为什么你这么关心不做另一个stackalloc?
  • 为什么不使用内置的字符串方法?

这看起来像是过早优化的情况。


编辑 1 - 实施

您发布的实现仅在两个跨度长度相同的情况下才有效。您在 cmets 中提到并非总是如此。在处理不同长度时,有几种边缘情况的组合:

// The best case - both sections are the same length,
// so you don't have to shuffle any other memory around
// It doesn't matter if there is a gap
[_, _, _, a, a, a, b, b, b, _, _, _]
[_, _, _, a, a, a, x, x, x, b, b, b, _, _, _]

// The sections are not the same length, there is no gap
[_, _, _, a, a, b, b, b, b, _, _, _]
[_, _, _, a, a, a, a, b, b, _, _, _]

// The sections are not the same length, and there is a gap
[_, _, _, a, a, x, x, x, b, b, b, b, _, _, _]
[_, _, _, a, a, a, a, x, x, x, b, b, _, _, _]

实现需要查看这些情况,并处理它们中的每一个。

public static void Swap<T>(Span<T> span, int indexA, int lengthA, int indexB, int lengthB)
{
    var a = span.Slice(indexA, lengthA);
    var b = span.Slice(indexB, lengthB);

    if (lengthA == lengthB)
    {
        // The easy implementation
        var temp = stackalloc T[lengthA];
        a.CopyTo(temp);
        b.CopyTo(a);
        temp.CopyTo(b);
        return;
    }

    var destinationA = span.Slice(indexB + lengthB - lengthA, lengthA);
    var destinationB = span.Slice(indexA, lengthB);
    var indexX = indexA + lengthA;
    if (indexX == indexB)
    {
        // There is no gap between a and b
        var temp = stackalloc T[lengthA];
        a.CopyTo(temp);
        b.CopyTo(destinationB);
        temp.CopyTo(destinationA);
    }
    else
    {
        // There is a gap 'x' between 'a' and 'b' that needs to be moved too
        var lengthX = indexB - indexX;
        var x = span.Slice(indexX, lengthX);
        var destinationX = span.Slice(indexA + lengthB, lengthX);
        var tempA = stackalloc T[lengthA];
        var tempX = stackalloc T[lengthX];
        a.CopyTo(tempA);
        x.CopyTo(tempX);
        b.CopyTo(destinationB);
        tempX.CopyTo(destinationX);
        tempA.CopyTo(destinationA);
    }
}

同样,您提到想要在不使用“stackalloc”的情况下执行此操作。为什么?您是否分析过您的代码,并发现您的交换方法存在瓶颈,特别是对“stackalloc”的调用?我想你会发现,与使用 for 循环而不是 Span.CopyTo()(可以使用 memcpy)的成本相比,这些对“stackalloc”的调用的性能可以忽略不计

【讨论】:

  • 嗨,谢谢你的回答!我的意思是不发生新副本很重要,因为这个东西来自一个流,我必须保留它的内存布局,除了我可以自由交换非常特定的跨度,但我不能只交换其中的一些,要么交换我从流中得到的特定跨度或根本不交换!
  • 我真的很想操纵原件,在这里考虑这个例子:
  • [UsefulSpan][IrrelavantSpan][UsefulSpan][IrrelavantSpan][UsefulSpan][IrrelavantSpan] 这应该是这样的:大小不一样!
  • 所以...你已经将整个内容加载到内存中?你不能只是交换任意大小的内存块,你必须把它全部洗牌,这样就没有间隙了。这是非常可行的,请尝试实施它并向我们展示您的尝试
  • 你看到我的编辑了吗?
猜你喜欢
  • 1970-01-01
  • 2021-01-14
  • 1970-01-01
  • 1970-01-01
  • 2021-07-09
  • 1970-01-01
  • 1970-01-01
  • 2016-04-04
  • 2017-03-21
相关资源
最近更新 更多