C#.NET Core 2.1跨度和内存性能注意事项

using System.Buffers;const byte carriageReturn = (byte)\r;const int arbitrarySliceStart = 5;// using MemoryTasync Taskint ReadAsyncWithMemory(Stream sourceStream, int bufferSize){var buffer =...

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);
    }
}

我阅读了有关Span< T>的MSDN和CodeMag文章,但我仍然对他们的表现有疑问.

我理解Span< T>比Memory< T>更具性能,但我想我想知道到什么程度.我发布了3个示例方法,我想知道哪种方法最好.

1.存储器< T>只要

第一个函数ReadAsyncWithMemory仅使用Memory< T>.处理工作,非常简单.

2.跨度< T>没有局部变量

在第二个函数中,ReadAsyncWithSpan,Span< T>代替使用,但没有创建局部变量,并且调用buffer.AsSpan(arbitrarySliceStart,bytesRead)两次,这看起来很笨拙.但是,如果Span< T>比Memory< T>更高效,是否值得双重调用?

2.跨度< T>附加功能

在第三个函数ReadAsyncWithSpanAndAdditionalFunction中,引入了局部函数,使得Span< T>可用于内存操作.现在的问题是,调用一个新函数并引入一个新的堆栈帧,值得使用Span< T>的性能提升.超过记忆< T>?

最后的问题

>为跨度添加局部变量是否会导致额外开销?

>仅仅内联Span< T>是否值得失去可读性没有将它分配给变量?

>调用附加功能以使用Span< T>超过记忆< T>值得新功能和堆栈框架的开销?
>记忆< T>比Span< T>显着更低的性能.什么时候它只被限制在堆栈框架而没有分配给堆?

解决方法:

错误:您的示例中存在一些错误/干扰(如果在问题之外进行了删除,请删除此部分).

> AsMemory / AsSpan取一个起始索引和长度,所以buffer.AsSpan(arbitrarySliceStart,bytesRead)是一个bug,可能只是buffer.AsSpan(0,bytesRead).如果你打算跳过第一个任意的SliceStart字节读取它应该是buffer.AsSpan(arbitrarySliceStart,bytesRead-arbitrarySliceStart),可能需要检查(bytesRead< arbitrarySliceStart).
>一个完整的例子,希望读取一个整数文本字段,从一个固定的偏移量开始流入一个流并由一个回车终止,这需要一个循环来确保读取“足够的”数据(……如果“太多”被读取则处理等等),但这不在手头的主题之内.

这个问题似乎是围绕编译器在异步函数中禁止Span局部变量.希望,如果Span变量的使用/生命周期不等待“调用”,未来的版本将不会强制执行此限制.

>为跨度添加局部变量是否会导致额外开销?

没有.

好吧,它可能导致组成Span的底层指针和长度字段的额外赋值/复制操作(尽管不是它们引用的内存范围).但即使这样也应该被优化掉,或者无论如何只需要中间/临时.

这不是编译器“不喜欢”Span变量的原因.跨度变量必须保留在堆栈上,或者引用的内存可能从它们下面收集,即只要它们保留在堆栈上,引用内存的SOMETHING ELSE必须仍然在堆栈上“低于它们”.异步/等待“函数”在每次等待调用时返回,然后在“等待”任务完成时作为继续/状态机调用继续.

注意:这不仅仅是托管内存和GC,否则必须检查Spans以获取对GC跟踪对象的引用.跨度可以指非托管内存或跟踪对象的块.

>在不将其分配给变量的情况下内联Span是否值得失去可读性?

嗯,这直接是一个风格/意见问题.但是,“重新创建”Span意味着函数调用但没有分配(只是堆栈操作和访问/复制几个整数大小的项目);并且呼叫本身将是JIT内联的良好候选者.

>调用一个额外的函数,以便使用Span over Memory值得新函数和堆栈帧的开销吗?

好吧,获取内存将需要函数调用和堆栈帧(以及堆内存分配).所以它取决于你重用那个内存的程度.并且……如果它没有被置于循环中或需要IO那么正常,那么性能可能不是问题.

但是,要小心你如何形成额外的功能.如果关闭变量(如示例中所示),编译器可能会发出堆分配来进行调用.

>当内存仅限于堆栈帧而未分配给堆时,内存的性能是否明显低于Span?

好吧,我不认为你可以堆叠内存< T> (本身),这是什么意思?

但是,与内存相比,Span避免了对索引进行一次偏移调整,因此如果循环执行大量索引,则在该循环外创建跨度将支付红利.这可能是为什么像SpanOf这样的方法是在Span上提供的,而不是在Memory上提供的.

>原始问题:哪个最好:内存< T&gt ;,没有区域设置变量,附加功能?
同样,这是一个样式/意见问题(除非您实际上描述了一个表现不佳的应用程序).

我的观点:仅在函数边界使用Span< T> s.仅将Memory< T> s用于成员变量.对于“内部”代码,只需使用开始/长度或开始/结束索引变量,并且名称清楚.清晰的名称将有助于避免更多的错误,而不是制作大量的Spans /“Slices”.如果函数太长以至于不再清楚变量意味着什么,那么无论如何都是时候考虑子函数了.

本文标题为:C#.NET Core 2.1跨度和内存性能注意事项

基础教程推荐