C# .NET Core 2.1 Span<T> 和 Memory<T> 性能注意事项

标签 c# .net memory memory-management .net-core

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

我读了 MSDNCodeMag关于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 添加局部变量会导致额外的开销吗?
  • 内联 Span<T> 是否值得失去可读性?不将其分配给变量?
  • 正在调用附加函数以使用 Span<T>Memory<T>值得新函数和堆栈帧的开销吗?
  • Memory<T>性能明显低于 Span<T>当它仅限于堆栈帧而不分配给堆时?
  • 最佳答案

    错误:您的示例中有一些错误/干扰(如果编辑出问题,请删除此部分)。

  • AsMemory/AsSpan 采用起始索引和 长度所以buffer.AsSpan(arbitrarySliceStart, bytesRead) 是一个错误 并且可能只是 buffer.AsSpan(0, bytesRead) .如果您打算跳过读取的第一个任意 SliceStart 字节,它应该是 buffer.AsSpan(arbitrarySliceStart, bytesRead-arbitrarySliceStart)可能会检查 (bytesRead > arbitrarySliceStart) .
  • 一个完整的例子,希望从一个固定的偏移量开始读取一个整数文本字段到一个流中,并以回车结束,需要一个循环来确保读取“足够”的数据(......如果读取了“太多”,则处理,等),但这超出了手头的主题。

  • 这个问题似乎是关于解决编译器在异步函数中不允许 Span 局部变量的问题。希望如果 Span 变量的使用/生命周期不交叉等待“调用”, future 版本不会强制执行此限制。
  • 为 span 添加局部变量会导致额外的开销吗?

  • 不。

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

    这不是编译器“不喜欢” Span 变量的原因。跨度变量必须留在堆栈上,否则引用的内存可能会从它们下面被收集起来,即只要它们留在堆栈上,引用内存的其他东西必须仍然在堆栈上“低于它们”。异步/等待“函数”在每次等待调用时返回,然后在“等待”任务完成时作为延续/状态机调用恢复。

    注意:这不仅仅是关于托管内存和 GC,否则必须检查 Span 以获取对 GC 跟踪对象的引用。跨度可以引用非托管内存或被跟踪对象的块。
  • 仅内联 Span 而不将其分配给变量是否值得失去可读性?

  • 嗯,这直接是一个风格/意见问题。然而,“重新创建”一个 Span 意味着一个函数调用但没有分配(只是堆栈操作和访问/复制一些整数大小的项目);并且调用本身将是 JIT 内联的一个很好的候选者。
  • 调用附加函数以使用跨内存是否值得新函数和堆栈帧的开销?

  • 获得内存需要函数调用和堆栈帧(以及堆内存分配)。因此,这取决于您重用该内存的程度。并且...正常情况下,如果它没有被埋在循环中或不需要 IO,那么性能可能不是问题。

    但是,请注意如何形成该额外功能。如果您关闭变量(如您的示例中所示),编译器可能会发出堆分配来进行该调用。
  • 当内存仅限于堆栈帧而不分配给堆时,它的性能是否明显低于 Span?

  • 好吧,我认为您不能堆栈分配 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/

    相关文章:

    java - 在java中使用大内存的最佳实践是什么?

    c++ - 动态创建 std::string 时调用两次 'new'

    c# - 为什么 "Using Nullable Types"上的 C# 编程指南列出了一个无效数组作为示例?

    .net - 带有 HTTPS 的 WCF WSDL 位置地址

    .net - 动态更改 Service Fabric 环境变量

    .net - Powershell 和覆盖比较运算符

    c++ - 如何将 "\327\220\327\250\327\225\327\236"转换为希伯来语?

    c# - 如何向窗口标题栏添加一个额外的按钮,以便它按标准工作?

    C# 缺少内容类型边界

    c# - 使用C#制作Maemo应用程序(代码转QT)