• 关于 Span 的一切:探索新的 .NET 明星: 3.什么是 Memory<T>,以及为什么你需要它?


    3. 什么是 Memory<T>,以及为什么你需要它?

    Span<T> 是包含 ref 字段的仿 ref 类型,并且 ref 字段可以不止于类似数组的开始位置,还可以是中间位置:

    var arr = new byte[100];
    Span<byte> interiorRef1 = arr.AsSpan(start: 20);
    Span<byte> interiorRef2 = new Span<byte>(arr, 20, arr.Length – 20);
    Span<byte> interiorRef3 =
      MemoryMarshal.CreateSpan<byte>(arr, ref arr[20], arr.Length – 20);
    

    这些引用被称为中间指针,跟踪它们对于 .NET 运行时的垃圾回收器来说是昂贵的操作。因此,运行时限制这些 ref 只能存在于堆栈,因为它提供了隐式的可能存在的中间指针数量的低限制。

    进一步说,如前所展示的那样,Span<T> 比机器的 word 类型尺寸更大,这意味着对 Span 的读、写操作不是原子操作。如果多个线程同时读、写同一个堆中的 Span 的字段,就会带来欲哭无泪的风险。想象一下一个已经初始化的 Span 包含一个有效的引用和一个相关的值为 50 的 _length 字段。一个线程开始在其上写新的 Span,并得到一个新的 _pointer 值。然后,在它设置相关的 _length 为 20 之前,第二个线程读取该 Span,它现在包含新的 _pointer 但是包含了旧的 _length 值。

    因此,Span<T> 实例只能在堆栈上存活,而不是堆上。这意味着你不能装箱 Span ( 因此对 Span<T> 使用反射 API,因为这要求装箱操作 )。这意味着你不能在类中定义 Span<T> 字段,甚至是非仿 ref 结构中。它意味着你不能在它们可能隐式成为类中字段的地方使用 Span,例如被捕获到 Lambda 或者在 async 方法中的本地变量,或者迭代器 ( 因为这些 locals 可能最终变成编译器生成的状态机字段 )。这也意味着你不能使用 Span<T> 作为范型参数,因为该类型的实例参数可能最终被装箱,或者存储在堆上 ( 这也是当前没有 where T: ref struct 约束存在的原因 )。

    这些限制对很多场景并不重要,特别是在计算密集和同步处理的函数中。但是异步函数就是另外一回事了。在本文开始引用的多数问题是围绕数组来的,数组切片、原生内存等等不管是同步还是异步都存在。然而,如果 Span<T> 不能存储在堆中,也就不能跨异步操作被持久化,答案是什么呢?Memory<T>。

    Memory<T> 看起来非常像 ArraySegment<T>:

    public readonly struct Memory<T>
    {
      private readonly object _object;
      private readonly int _index;
      private readonly int _length;
      ...
    }
    

    你可以通过数组来创建 Memory<T>,并像在 Span 中一样进行切片,但是它是非仿 ref 结构,可以保存到堆中。而且,当你希望进行同步操作的时候,你可以通过它获得一个 Span<T>,例如:

    static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream)
    {
      int bytesRead = await stream.ReadAsync(buffer);
      return Checksum(buffer.Span.Slice(0, bytesRead));
      // Or buffer.Slice(0, bytesRead).Span
    }
    static int Checksum(Span<byte> buffer) { ... }
    

    与 Span<T> 和 ReadOnlySpan<T> 一样,Memory<T> 也有一个只读的等价物:ReadOnlyMemory<T>。如你所愿,它的 Span 属性返回一个 ReadOnlySpan<T>。图 1 提供了在它们之间进行转换的内建支持的总结。

    图 1 在 Span 相关的类型之间进行不分配内存/不复制的转换

    来源 目标 机制
    ArraySegment Memory Implicit cast, AsMemory method
    ArraySegment ReadOnlyMemory Implicit cast, AsMemory method
    ArraySegment ReadOnlySpan Implicit cast, AsSpan method
    ArraySegment Span Implicit cast, AsSpan method
    ArraySegment T[] Array property
    Memory ArraySegment MemoryMarshal.TryGetArray method
    Memory ReadOnlyMemory Implicit cast, AsMemory method
    Memory Span Span property
    ReadOnlyMemory ArraySegment MemoryMarshal.TryGetArray method
    ReadOnlyMemory ReadOnlySpan Span property
    ReadOnlySpan ref readonly T Indexer get accessor, marshaling methods
    Span ReadOnlySpan Implicit cast, AsSpan method
    Span ref T Indexer get accessor, marshaling methods
    String ReadOnlyMemory AsMemory method
    String ReadOnlySpan Implicit cast, AsSpan method
    T[] ArraySegment Ctor, Implicit cast
    T[] Memory Ctor, Implicit cast, AsMemory method
    T[] ReadOnlyMemory Ctor, Implicit cast, AsMemory method
    T[] ReadOnlySpan Ctor, Implicit cast, AsSpan method
    T[] Span Ctor, Implicit cast, AsSpan method
    void* ReadOnlySpan Ctor
    void* Span Ctor

    你会注意到,Memory<T> 的 _object 字段不是强类型的 T[]; 而是存储了一个对象。这说明了 Memory<T> 可以封装数组之外的数据,例如 System.Buffers.OwnedMemory<T>。OwnedMemory<T> 是一个抽象类,可以用来封装需要拥有自己的生命周期管理的数据,例如从池中获得的内存。这是比本文更为高级的话题,但是它展示了如何使用 Memory<T>,例如,将指针封装到原生内存中。ReadOnlyMemory<char> 也可以用于字符串,如同 ReadOnlySpan<char> 一样。

  • 相关阅读:
    关于Hadoop分组排序
    关闭Xshell Linux服务关闭问题
    Centos 7 mysql的安装
    通过 Thumbnails 压缩图片后转 base64
    Centos 7 java环境安装
    mysql 批量添加
    SpringCloud_服务提供者关闭,eureka还显示问题
    关于Hutool发送邮件
    Spring 定时任务 @Scheduled注解
    Listary快捷键和idea快捷键冲突问题
  • 原文地址:https://www.cnblogs.com/haogj/p/16228243.html
Copyright © 2020-2023  润新知