using System.Buffers;
const byte carriageReturn = (byte)'\r';
const int arbitrarySliceStart = 5;
// using Memory<T>
async Task<int> ReadAsyncWithMemory(Stream sourceStream, int bufferSize)
{
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
var bytesRead = await sourceStream.ReadAsync(buffer);
var memory = buffer.AsMemory(arbitrarySliceStart, bytesRead);
var endOfNumberIndex = memory.Span.IndexOf(carriageReturn);
var memoryChunk = memory.Slice(0, endOfNumberIndex);
var number = BitConverter.ToInt32(memoryChunk.Span);
ArrayPool<byte>.Shared.Return(buffer);
return number;
}
// using Span<T> without assigning to variable
async Task<int> ReadAsyncWithSpan(Stream sourceStream, int bufferSize)
{
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
var bytesRead = await sourceStream.ReadAsync(buffer);
var endOfNumberIndex = buffer.AsSpan(arbitrarySliceStart, bytesRead).IndexOf(carriageReturn);
var number = BitConverter.ToInt32(buffer.AsSpan(arbitrarySliceStart, bytesRead).Slice(0, endOfNumberIndex));
ArrayPool<byte>.Shared.Return(buffer);
return number;
}
// using Span<T> with additional local or private function
async Task<int> ReadAsyncWithSpanAndAdditionalFunction(Stream sourceStream, int bufferSize)
{
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
var bytesRead = await sourceStream.ReadAsync(buffer);
var number = SliceNumer();
ArrayPool<byte>.Shared.Return(buffer);
return number;
int SliceNumer()
{
var span = buffer.AsSpan(arbitrarySliceStart, bytesRead);
var endOfNumberIndex = span.IndexOf(carriageReturn);
var numberSlice = span.Slice(0, endOfNumberIndex);
return BitConverter.ToInt32(numberSlice);
}
}
我读了 MSDN和 CodeMag关于
Span<T>
的文章,但我仍然对他们的表现有疑问。我明白
Span<T>
性能高于 Memory<T>
,但我想我想知道到什么程度。我发布了 3 个示例方法,我想知道哪种方法最好。1.
Memory<T>
只有第一个函数,
ReadAsyncWithMemory
, 只使用 Memory<T>
处理工作,非常简单。2.
Span<T>
没有局部变量 在第二个函数中,
ReadAsyncWithSpan
, Span<T>
改为使用,但没有创建局部变量,并且调用 buffer.AsSpan(arbitrarySliceStart, bytesRead)
做了两次,看起来很笨重。但是,如果 Span<T>
性能高于 Memory<T>
,值得双重呼吁吗?2.
Span<T>
带附加功能 在第三个函数中,
ReadAsyncWithSpanAndAdditionalFunction
,引入了一个局部函数,使得 Span<T>
可用于内存操作。现在的问题是,调用一个新函数并引入一个新的堆栈帧值得使用 Span<T>
带来的性能提升。在 Memory<T>
?最后问题
Span<T>
是否值得失去可读性?不将其分配给变量? Span<T>
在 Memory<T>
值得新函数和堆栈帧的开销吗? Memory<T>
性能明显低于 Span<T>
当它仅限于堆栈帧而不分配给堆时? 最佳答案
错误:您的示例中有一些错误/干扰(如果编辑出问题,请删除此部分)。
buffer.AsSpan(arbitrarySliceStart, bytesRead)
是一个错误 并且可能只是 buffer.AsSpan(0, bytesRead)
.如果您打算跳过读取的第一个任意 SliceStart 字节,它应该是 buffer.AsSpan(arbitrarySliceStart, bytesRead-arbitrarySliceStart)
可能会检查 (bytesRead > arbitrarySliceStart)
. 这个问题似乎是关于解决编译器在异步函数中不允许 Span 局部变量的问题。希望如果 Span 变量的使用/生命周期不交叉等待“调用”, future 版本不会强制执行此限制。
不。
好吧,它可能会导致组成 Span 的底层指针和长度字段的额外赋值/复制操作(尽管 不是 它们引用的内存范围)。但即使这样也应该被优化掉,或者无论如何都可能只在中间/临时文件中发生。
这不是编译器“不喜欢” Span 变量的原因。跨度变量必须留在堆栈上,否则引用的内存可能会从它们下面被收集起来,即只要它们留在堆栈上,引用内存的其他东西必须仍然在堆栈上“低于它们”。异步/等待“函数”在每次等待调用时返回,然后在“等待”任务完成时作为延续/状态机调用恢复。
注意:这不仅仅是关于托管内存和 GC,否则必须检查 Span 以获取对 GC 跟踪对象的引用。跨度可以引用非托管内存或被跟踪对象的块。
嗯,这直接是一个风格/意见问题。然而,“重新创建”一个 Span 意味着一个函数调用但没有分配(只是堆栈操作和访问/复制一些整数大小的项目);并且调用本身将是 JIT 内联的一个很好的候选者。
获得内存需要函数调用和堆栈帧(以及堆内存分配)。因此,这取决于您重用该内存的程度。并且...正常情况下,如果它没有被埋在循环中或不需要 IO,那么性能可能不是问题。
但是,请注意如何形成该额外功能。如果您关闭变量(如您的示例中所示),编译器可能会发出堆分配来进行该调用。
好吧,我认为您不能堆栈分配
Memory<T>
(本身),这是什么意思?但是,与 Memory 相比,Span 避免了对索引的一次偏移调整,因此如果您循环遍历大量索引,则在该循环之外创建 Span 将带来好处。这可能就是为什么像 IndexOf 这样的方法被提供在 Span 上,而不是 Memory 的原因。
Memory<T>
,没有局部变量,附加功能? 这又是一个风格/意见问题(除非您实际上分析了一个性能不佳的应用程序)。
我的意见:只用
Span<T>
s 在函数边界。仅使用 Memory<T>
s 为成员变量。对于“内部”代码,只需使用开始/长度或开始/结束索引变量并清楚地命名它们。与制作大量跨度/“切片”相比,清晰的名称将有助于避免更多错误。如果函数太长以至于不再清楚变量的含义,那么无论如何是时候考虑子函数了。
关于C# .NET Core 2.1 Span<T> 和 Memory<T> 性能注意事项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51365706/