繁体   English   中英

将字节数组写入Span并使用Memory发送

[英]Writing byte array to Span and sending it with Memory

我收到一个缓冲区,我想从它创建一个新的缓冲区(连接字节前缀,混合和后缀),然后将其发送到套接字。

例如:初始缓冲区: "aaaa"
最终缓冲区: "$4\\r\\naaaa\\r\\n" (Redis RESP协议 - 批量字符串)

我怎样才能将span转换为memory (我不知道是否应该使用stackalloc ,因为我不知道输入buffer多大。我认为它会更快)。

        private static readonly byte[] RESP_BULK_ID =BitConverter.GetBytes('$');
        private static readonly byte[] RESP_FOOTER = Encoding.UTF8.GetBytes("\r\n");


        static Memory<byte> GetNodeSpan(in ReadOnlyMemory<byte> payload) {

            ReadOnlySpan<byte> payloadHeader = BitConverter.GetBytes(payload.Length);

            Span<byte> result = stackalloc byte[
                                                    RESP_BULK_ID.Length +
                                                    payloadHeader.Length + 
                                                    RESP_FOOTER.Length + 
                                                    payload.Length + 
                                                    RESP_FOOTER.Length
                                                    ];
            Span<byte> cursor = result;

            RESP_BULK_ID.CopyTo(cursor);
            cursor=cursor.Slice(RESP_BULK_ID.Length);

            payloadHeader.CopyTo(cursor);
            cursor = cursor.Slice(payloadHeader.Length);

            RESP_FOOTER.CopyTo(cursor);
            cursor = cursor.Slice(RESP_FOOTER.Length);

            payload.Span.CopyTo(cursor);
            cursor = cursor.Slice(payload.Span.Length);

            RESP_FOOTER.CopyTo(cursor);

            return new Memory<byte>(result.AsBytes()) // ?can not convert from span to memory ,and cant return span because it can be referenced outside of scope
        }

PS:我应该使用old-school for循环而不是CopyTo吗?

Memory<T>旨在将一些托管对象(例如数组)作为目标。 Memory<T>转换为Span<T>然后简单地将目标对象固定在内存中并使用它的地址来构造Span<T> 但是不能进行对等转换 - 因为Span<T>可以指向不属于任何托管对象的内存部分(非托管内存,堆栈等),因此无法直接将Span<T>转换为Memory<T> (实际上有这样的方法,但它涉及实现类似于NativeMemoryManager的自己的MemoryManager<T> ,是不安全和危险的,我很确定它不是你想要的)。

使用stackalloc是一个坏主意,原因有两个:

  1. 由于您不知道advace中有效负载的大小,如果有效负载太大,您可能很容易得到StackOverflowException

  2. (正如您的源代码中的注释已经暗示的那样)尝试返回当前方法堆栈上分配的内容是一个可怕的想法,因为它可能会导致数据损坏或应用程序崩溃。

在堆栈上返回结果的唯一方法是要求GetNodeSpan调用者GetNodeSpanstackalloc内存转换为stackalloc ,将其转换为Span<T>并将其作为附加参数传递。 问题是(1) GetNodeSpan调用者必须知道要分配多少,(2)不会帮助您将Span<T>转换为Memory<T>

因此,要存储结果,您需要在堆上分配对象。 简单的解决方案就是分配新的数组,而不是stackalloc 然后可以使用这样的数组来构造Span<T> (用于复制)以及Memory<T> (用作方法结果):

static Memory<byte> GetNodeSpan(in ReadOnlyMemory<byte> payload)
{
    ReadOnlySpan<byte> payloadHeader = BitConverter.GetBytes(payload.Length);

    byte[] result = new byte[RESP_BULK_ID.Length +
                             payloadHeader.Length +
                             RESP_FOOTER.Length +
                             payload.Length +
                             RESP_FOOTER.Length];

    Span<byte> cursor = result;

    // ...

    return new Memory<byte>(result);
}

显而易见的缺点是您必须为每个方法调用分配新数组。 为避免这种情况,您可以使用内存池,其中重用已分配的数组:

    static IMemoryOwner<byte> GetNodeSpan(in ReadOnlyMemory<byte> payload)
    {
        ReadOnlySpan<byte> payloadHeader = BitConverter.GetBytes(payload.Length);

        var result = MemoryPool<byte>.Shared.Rent(
                                    RESP_BULK_ID.Length +
                                    payloadHeader.Length +
                                    RESP_FOOTER.Length +
                                    payload.Length +
                                    RESP_FOOTER.Length);

        Span<byte> cursor = result.Memory.Span;

        // ...

        return result;
    }

请注意,此解决方案返回IMemoryOwner<byte> (而不是Memory<T> )。 调用者可以使用IMemoryOwner<T>.Memory属性访问Memory<T> ,并且必须调用IMemoryOwner<byte>.Dispose()以在不再需要内存时将数组返回池。 需要注意的第二件事是MemoryPool<byte>.Shared.Rent()实际上可以返回超过所需最小值的数组。 因此,您的方法可能还需要返回结果的实际长度(例如作为out参数),因为IMemoryOwner<byte>.Memory.Length可以返回比实际复制到结果更多的内容。

PS:我希望for循环来拷贝很短的阵列(如果有的话),在那里你可以通过避免方法调用保存几个CPU周期是稍快而已。 但是Span<T>.CopyTo()使用优化的方法,可以一次复制几个字节,并且(我坚信)使用特殊的CPU指令来复制内存块,因此应该快得多。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM