我收到一个缓冲区,我想从它创建一个新的缓冲区(连接字节前缀,固定和后固定),然后将其发送到套接字.
例如:初始缓冲区:"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
}
Run Code Online (Sandbox Code Playgroud)
PS:我应该使用老式for循环代替CopyTo吗?
Ňuf*_*Ňuf 10
Memory<T>旨在将一些托管对象(例如数组)作为目标.转换Memory<T>为Span<T>然后简单地将目标对象固定在内存中并使用它的地址来构造Span<T>.但是对等转换是不可能的 - 因为Span<T>可以指向不属于任何托管对象的内存部分(非托管内存,堆栈等),不可能直接转换Span<T>为Memory<T>.(实际上有办法实现这一点,但它涉及实现MemoryManager<T>类似于NativeMemoryManager的类似,是不安全和危险的,我很确定它不是你想要的).
使用stackalloc是一个坏主意有两个原因:
由于您不知道有效载荷的大小,因此StackOverflowException如果有效载荷太大,您可以轻松获得.
(正如您的源代码中的注释已经暗示的那样)尝试返回当前方法堆栈上分配的内容是一个可怕的想法,因为它可能会导致数据损坏或应用程序崩溃.
只有这样,才能在堆栈上返回的结果将需要调用者GetNodeSpan到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);
}
Run Code Online (Sandbox Code Playgroud)
显而易见的缺点是您必须为每个方法调用分配新数组.为避免这种情况,您可以使用内存池,其中重用已分配的数组:
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;
}
Run Code Online (Sandbox Code Playgroud)
请注意,此解决方案返回IMemoryOwner<byte>(而不是Memory<T>).调用者可以使用属性访问Memory<T>,IMemoryOwner<T>.Memory并且必须调用IMemoryOwner<byte>.Dispose()以在不再需要内存时将数组返回池.需要注意的第二件事是, MemoryPool<byte>.Shared.Rent()实际上可以返回超过所需最小值的数组.因此,您的方法可能还需要返回结果的实际长度(例如作为out参数),因为IMemoryOwner<byte>.Memory.Length可以返回比实际复制到结果更多的结果.
PS:我希望for循环只能快速复制非常短的数组(如果有的话),你可以通过避免方法调用来节省一些CPU周期.但是Span<T>.CopyTo()使用可以一次复制多个字节的优化方法(我坚信)使用特殊的CPU指令来复制内存块,因此应该快得多.
| 归档时间: |
|
| 查看次数: |
2965 次 |
| 最近记录: |