将 ReadOnlySequence<byte> 复制到 Struct 的更好方法
A better way to copy a ReadOnlySequence<byte> to a Struct
所以我最近一直在使用 System.Buffers
,特别是 ReadOnlySequence<T>
class。
我有一个基元结构,定义如下:
[StructLayout(LayoutKind.Sequential,Pack =1,CharSet=CharSet.Unicode)]
public partial struct MessageHeader
{
public MessageVersion Version;
...
我可以毫无问题地在网络上来回传递这个信息,我正在利用 System.IO.Pipelines
为此。
将 ReadOnlySequence<byte>
转换回结构引起了一些头痛。
我从这个开始:
private void ExtractMessageHeaderOld(ReadOnlySequence<byte> ros, out MessageHeader messageHeader)
{
var x = ros.ToArray<byte>();
ReadOnlySpan<MessageHeader> mhSpan = MemoryMarshal.Cast<byte, MessageHeader>(x);
messageHeader = mhSpan[0];
}
在服务器的整个生命周期中创建了数千个小字节[]数组,(这本身不是问题),但是系统正在尝试做的所有其他事情都会增加一些压力在 GC 上。
所以我开始使用:
private void ExtractMessageHeader(ReadOnlySequence<byte> ros, out MessageHeader messageHeader, ref byte[] workingSpace)
{
var i = 0;
foreach (var rom in ros)
foreach (var b in rom.Span)
{
workingSpace[i] = b;
i++;
}
ReadOnlySpan<MessageHeader> mhSpan = MemoryMarshal.Cast<byte, MessageHeader>(workingSpace);
messageHeader = mhSpan[0];
}
这不会进行任何内存分配,但我只是觉得应该有更好的方法。
foreach
循环,MemoryMarshal.Cast()
到 messageHeader[1]
这样我就可以提取元素 0 是我在阅读源代码时偶然发现的一个 hack。
我找不到一个 API 来将 ReadOnlySequence<bytes>
的内容干净地提取到 messageHeader
中,而当我拥有的东西在工作时,我只是觉得应该存在...
编辑 1:
我刚刚偶然发现 BuffersExtensions.CopyTo<T>(ReadOnlySequence<T>, Span<T>)
可以用
替换 foreach 循环
ros.CopyTo(workingSpace);
您可以通过使用 stackalloc
和 Span<byte>
在第一种方法中缓冲数据来稍微降低 GC 压力,如下所示:
public static void ExtractMessageHeaderOld(ReadOnlySequence<byte> ros, out MessageHeader messageHeader)
{
Span<byte> stackSpan = stackalloc byte[(int)ros.Length];
ros.CopyTo(stackSpan);
ReadOnlySpan<MessageHeader> mhSpan = MemoryMarshal.Cast<byte, MessageHeader>(stackSpan);
messageHeader = mhSpan[0];
}
这可能会使速度更快,但您必须对其进行检测以查看它是否真的有帮助。
所以我最近一直在使用 System.Buffers
,特别是 ReadOnlySequence<T>
class。
我有一个基元结构,定义如下:
[StructLayout(LayoutKind.Sequential,Pack =1,CharSet=CharSet.Unicode)]
public partial struct MessageHeader
{
public MessageVersion Version;
...
我可以毫无问题地在网络上来回传递这个信息,我正在利用 System.IO.Pipelines
为此。
将 ReadOnlySequence<byte>
转换回结构引起了一些头痛。
我从这个开始:
private void ExtractMessageHeaderOld(ReadOnlySequence<byte> ros, out MessageHeader messageHeader)
{
var x = ros.ToArray<byte>();
ReadOnlySpan<MessageHeader> mhSpan = MemoryMarshal.Cast<byte, MessageHeader>(x);
messageHeader = mhSpan[0];
}
在服务器的整个生命周期中创建了数千个小字节[]数组,(这本身不是问题),但是系统正在尝试做的所有其他事情都会增加一些压力在 GC 上。
所以我开始使用:
private void ExtractMessageHeader(ReadOnlySequence<byte> ros, out MessageHeader messageHeader, ref byte[] workingSpace)
{
var i = 0;
foreach (var rom in ros)
foreach (var b in rom.Span)
{
workingSpace[i] = b;
i++;
}
ReadOnlySpan<MessageHeader> mhSpan = MemoryMarshal.Cast<byte, MessageHeader>(workingSpace);
messageHeader = mhSpan[0];
}
这不会进行任何内存分配,但我只是觉得应该有更好的方法。
foreach
循环,MemoryMarshal.Cast()
到 messageHeader[1]
这样我就可以提取元素 0 是我在阅读源代码时偶然发现的一个 hack。
我找不到一个 API 来将 ReadOnlySequence<bytes>
的内容干净地提取到 messageHeader
中,而当我拥有的东西在工作时,我只是觉得应该存在...
编辑 1:
我刚刚偶然发现 BuffersExtensions.CopyTo<T>(ReadOnlySequence<T>, Span<T>)
可以用
ros.CopyTo(workingSpace);
您可以通过使用 stackalloc
和 Span<byte>
在第一种方法中缓冲数据来稍微降低 GC 压力,如下所示:
public static void ExtractMessageHeaderOld(ReadOnlySequence<byte> ros, out MessageHeader messageHeader)
{
Span<byte> stackSpan = stackalloc byte[(int)ros.Length];
ros.CopyTo(stackSpan);
ReadOnlySpan<MessageHeader> mhSpan = MemoryMarshal.Cast<byte, MessageHeader>(stackSpan);
messageHeader = mhSpan[0];
}
这可能会使速度更快,但您必须对其进行检测以查看它是否真的有帮助。