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> 一样。

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

  1. Android开发艺术探索——新的征程,程序人生路漫漫!

    Android开发艺术探索--新的征程,程序人生路漫漫! 偶尔写点东西分享,但是我还是比较喜欢写笔记,看书,群英传看完了,是学到了点东西,开始看这本更加深入Android的书籍了,不知道适不适合自己, ...

  2. Dual Path Networks(DPN)——一种结合了ResNet和DenseNet优势的新型卷积网络结构。深度残差网络通过残差旁支通路再利用特征,但残差通道不善于探索新特征。密集连接网络通过密集连接通路探索新特征,但有高冗余度。

    如何评价Dual Path Networks(DPN)? 论文链接:https://arxiv.org/pdf/1707.01629v1.pdf在ImagNet-1k数据集上,浅DPN超过了最好的Re ...

  3. 探索新冠肺炎(COVID-19)对全球航班的影响

    Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 随着今天从欧洲到美国的旅行限制生效,以及为了减缓新冠病毒的传播更 ...

  4. C# - Span 全面介绍:探索 .NET 新增的重要组成部分

    假设要公开特殊化排序例程,以就地对内存数据执行操作.可能要公开需要使用数组的方法,并提供对相应 T[] 执行操作的实现.如果方法的调用方有数组,且希望对整个数组进行排序,这样做就非常合适.但如果调用方 ...

  5. 红星美凯龙CEO车建新的圆融和霸气

    待人接物中车建新有许多习惯,与别人一起行走时,走在靠马路的一边:吃饭时最好的菜留给客人.他说,做人往往就在细节中,别小看一个举动,无意中就会感染别人.和别人在一起,你要时时刻刻先考虑对方. 细节上体察 ...

  6. 浏览器拦截js打开新窗口

    最近做项目时,遇到的问题"想通过javascript在浏览器新标签页或新窗口打开一个新的页面,结果被浏览器大大无情给拦截了"业务需求:前端提交数据到后端,后端返回url,然后在新窗 ...

  7. 【公众号系列】SAP的新零售

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[公众号系列]SAP的新零售   写在前面 还是 ...

  8. 渐入OO课的深处,探索多线程的秘密——OO第二次博客总结

    一次又一次的挑战,一次又一次全新的知识,我来到了多线程的面前 第五次作业 1.度量分析 >第五次作业由于很大程度上调用的是前两次电梯的一些代码,所以存在的问题与前几次也十分相似.同时由于第一次使 ...

  9. [译]java8新特性:函数式编程(functional programming)的优点

    Java8引入了函数式编程,他对java是一个极大的扩展.Java从此不在是一个单纯的面向对象语言,现在他同时混合了函数式编程.这是巨大的改变,需要我们调整面对对象的编程习惯,以适应这些变化. 但是为 ...

  10. Java基础学习总结(33)——Java8 十大新特性详解

    Java8 十大新特性详解 本教程将Java8的新特新逐一列出,并将使用简单的代码示例来指导你如何使用默认接口方法,lambda表达式,方法引用以及多重Annotation,之后你将会学到最新的API ...

随机推荐

  1. LeetCode 1819. 序列中不同最大公约数的数目(数论)

    题目描述 给你一个由正整数组成的数组 nums . 数字序列的 最大公约数 定义为序列中所有整数的共有约数中的最大整数. 例如,序列 [4,6,16] 的最大公约数是 2 . 数组的一个 子序列 本质 ...

  2. iOS本地化NSLocalizedString的使用小结

    在iOS设备,包括iPhone和iPad是全球可用.显然,iOS用户都来自不同国家,说着不同的语言.为了提供出色的用户体验,你可能希望以多种语言提供您的应用程序.适应应用程序以支持特定语言的过程通常被 ...

  3. Android复习(三)清单文件中的元素——>uses-feature

    <uses-feature> Google Play 会利用应用清单中声明的 <uses-feature> 元素,从不符合应用硬件和软件功能要求的设备上过滤该应用. 通过指定应 ...

  4. AOT漫谈专题(第四篇): C#程序如何编译成Native代码

    一:背景 1. 讲故事 大家都知道所谓的.NET Native AOT即通过AOT编译器直接将C#代码编译成机器码,大家也习惯用C/C++的编译过程来类比,都是静态编译本质上都差不多,这篇我们借助工具 ...

  5. 在C#中基于Semantic Kernel的检索增强生成(RAG)实践

    Semantic Kernel简介 玩过大语言模型(LLM)的都知道OpenAI,然后微软Azure也提供了OpenAI的服务:Azure OpenAI,只需要申请到API Key,就可以使用这些AI ...

  6. 网络应用模型的经典分类:C/S 与 P2P

    网络应用无处不在,从我们日常使用的社交媒体到远程文件传输,网络应用模型是支撑这些通信和服务的基础架构.通常,这些网络应用根据其结构原理可以被大致划分为两大类:C/S(Client/Server)模型和 ...

  7. Analysis I Chapter 2 - By Professor Terence Tao

    目录 2.1 The Peano Axioms 2.2 Addition - Exercises 2.2 - 2.3 Multiplication - Exercises 2.3 - Method o ...

  8. burpsuit无法启动

    在安装burpsuit时,最难的并不是找带有注册机的burpsuit安装包. 而是因为一些底层的原因,无法打开 burpsuit. 提示 Your JRE appears to be version ...

  9. oracle下拼同比环比查询sql方法

    拼接方法: /// <summary> /// 生成计算同比环比查询语句 /// table:表名称:statColumns:要统计的值字段;yearColumn:年份字段名:monthC ...

  10. Docker容器与守护进程运维 --项目四

    一.Docker容器配置进阶 1.容器的自动重启 Docker提供重启策略控制容器退出时或Docker重启时是否自动启动该容器. 容器默认不支持自动重启,要使用 --restart 选项指定重启策略. ...