4. Span<T> 和 Memory<T> 是如何与 .NET 库集成的?

在前面的 Memory<T> 代码片段中,你应该注意到,在调用 Stream.ReadAsync() 的方法中传递了 Memory<byte> 参数。但是现在的 .NET 中的 Stream.ReadAsnc() 实际上接受类型为 byte[]。这是怎么工作的呢?

为了支持 Span<T> 和相关类型,数百个新的成员和类型被添加到 .NET 的各个部分。多数是现在的基于数组和基于字符串的重载方法,其它全新的类型则专注于特性的处理领域。例如,所有的基础类型,例如 Int32 现在在支持字符串的基础上,拥有支持 ReadOnlySpan 的 Parse 重载。想象一下,当你期望处理由两个数字通过逗号连接起来的字符串 ( 例如:123,456 ),你希望解析出来这两个数字,现在你可以如下编码:

string input = ...;
int commaPos = input.IndexOf(',');
int first = int.Parse(input.Substring(0, commaPos));
int second = int.Parse(input.Substring(commaPos + 1));

不过,这导致了两次字符串分配。如果你在编写性能敏感的代码,那么两次字符串分配就太多了,相反,你可以如下完成:

string input = ...;
ReadOnlySpan<char> inputSpan = input;
int commaPos = input.IndexOf(',');
int first = int.Parse(inputSpan.Slice(0, commaPos));
int second = int.Parse(inputSpan.Slice(commaPos + 1));

通过使用新的基于 Span 的 Parse 重载,你可以使得整个操作无内存分配发生。类似的解析和格式化方法也出现在基础类型,比如 Int32 中,这贯穿了核心类型,例如 DateTime、TimeSpan 和 Guid 中,以至于更高级别的类型,例如 BigInteger 和 IPAddress 中。

实际上,众多此类方法已经被添加到整个框架中。从 System.Random 到 System.Text.StringBuffer 到 System.Net.Sockets,这些添加的重载使得处理 {ReadOnly}Span<T> 和 {ReadOnly}Memory<T> 更为简单和高效。有些还带来了额外的优点。例如,Stream 现在由这个方法:

public virtual ValueTask<int> ReadAsync(
  Memory<byte> destination,
  CancellationToken cancellationToken = default) { ... }

你将会注意到与现有的 ReadAsync() 方法接受一个 byte[] 并返回一个 Task<int> 不同,这个重载方法不仅接受一个 Memory\<byte> 来代替 byte[],还返回一个 ValueTask\<int> 而不是 Task\<int>ValueTask\<int> 是用来帮助避免内存分配的结构,异步方法被频繁期待同步返回的场景,与我们缓存所有通常返回值不同。例如,运行时可以缓存完成的 Task<bool> 的值 true,和另外一个 false,但是它不能对于 Task<int> 所对应的所有整数返回值。

因为对于流的实现很常见的场景是通过某种方式缓存,使得 ReadAsync() 方法调用同步使用,这个新的 ReadAsync() 重载方法返回 ValueTask<int> 值。这意味着同步完成的异步流读取操作可以完全避免内存分配。ValueTask<int> 也用于其它的重载,比如 Socket.ReceiveAsync(),Socket.SendAsync(),WebSocket.ReceiveAsync() 和 TextReader.ReadAsync()。

此外,在某些地方,Span<T> 允许框架包含过去引起内存安全问题的方法。请考虑这样一种场景:你希望创建一个包含随机生成值的字符串,例如某种 ID。今天,你可能会编写需要分配 char 数组的代码,如下所示:

int length = ...;
Random rand = ...;
var chars = new char[length];
for (int i = 0; i < chars.Length; i++)
{
  chars[i] = (char)(rand.Next(0, 10) + '0');
}
string id = new string(chars);

你可以使用堆栈分配内存,进而获得 Span<char> 的好处,来避免需要使用不安全的代码。这种方式还可以对接受 ReadOnlySpan<char> 的字符串构造函数获得好处,例如:

int length = ...;
Random rand = ...;
Span<char> chars = stackalloc char[length];
for (int i = 0; i < chars.Length; i++)
{
  chars[i] = (char)(rand.Next(0, 10) + '0');
}
string id = new string(chars);

这种方式更好,这样你可以避免堆内存分配,但是你仍然被强制要求复制在堆栈上生成的数据复制到字符串中。这种方式还只能工作在当需要的内存足够小到可以在堆栈分配。如果长度足够小,例如 32 个字节,这样很不错,但是,如果是上千字节的话,会很容易导致堆栈溢出。怎么样可以直接写入到字符串的内存中呢?Span<T> 支持你做到这一点。除了字符串的新构造函数,字符串还有一个新的 Create() 方法:

public static string Create<TState>(
  int length, TState state, SpanAction<char, TState> action);
...
public delegate void SpanAction<T, in TArg>(Span<T> span, TArg arg);

该方法被实现为分配字符串空间,然后返回一个可写入的 Span,以便你可以在字符串构造之后填充其内容。注意,Span 本质上在堆栈操作对该场景是优势,生成这个 Span ( 指向字符串的内部存储 ) 将在字符串构造完成之前存在,使得它不可能在构造完成之后使用该 Span 来改变字符串。

int length = ...;
Random rand = ...;
string id = string.Create(length, rand, (Span<char> chars, Random r) =>
{
  for (int i = 0; chars.Length; i++)
  {
    chars[i] = (char)(r.Next(0, 10) + '0');
  }
});

现在,你不仅可以避免内存分配,你还直接写入在堆上分配的字符串内存,这意味着你也避免了内存复制,并且也没有被堆栈的尺寸所限制。

除了核心框架类型中新增加的成员,众多新的 .NET 类型也被开发出来与 Span 一起工作来高效处理特定的场景。例如,开发人员寻找编写重度涉及文本处理的高性能的微服务和 Web 站点,当处理 UTF-8 编码字符串的时候,如果不会从字符串编码和解码就会获得显著的性能提升。为支持这种处理,新的类型比如 System.Buffers.Text.Base64、System.Buffers.Text.Utf8Parser 和 System.Buffers.Text.Utf8Formatter 被添加进来。这些对于字节的 Span 操作,不仅避免了 Unicode 编码和解码,而且可以使得它们与原生缓冲区协作,这对于非常低级的多种网络栈来说很常见。

ReadOnlySpan<byte> utf8Text = ...;
if (!Utf8Parser.TryParse(utf8Text, out Guid value,
  out int bytesConsumed, standardFormat = 'P'))
  throw new InvalidDataException();

所有这些功能不仅可以公共使用,框架本身也从基于 Span<T> 和 Memory<T> 的方法中获得更好的性能。跨 .NET Core 的呼叫站点已切换到使用新的 ReadAsync 重载,以避免不必要的分配。原来完成的通过分配子字符串的解析,现在通过无分配的解析获益。甚至更小的类型,例如 Rfc2898DeriveBytes 也使用了这种方式,在 System.Security.Cryptography.HashAlgorithm 中的 TryComputeHash() 方法使用新的基于 Span 的方式,在分配内存上获得巨大的优势 ( 算法的每次迭代分配一个字节数组,这可能会迭代上千次 )。以及吞吐量的提高。

这些不止于核心 .NET 库;它延展到所有的技术栈。ASP.NET Core 现在重度依赖于 Span,例如,Kestrel 服务器的 HTTP 解析器基于此开发。将来,Span 很可能会从较低级别的 ASP.NET Core(例如其中间件管道)中的公共 API 中公开。

关于 Span 的一切:探索新的 .NET 明星: 4. Span<T> 和 Memory<T> 是如何与 .NET 库集成的?的更多相关文章

  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. span 右浮动折行 解决ie6/7中span右浮动折行问题

    A floated box is shifted to the left or right until its outer edge touches the containing block edge ...

  5. span 右浮动折行 解决ie6/7中span右浮动折行问题

    RM8005: IE6 IE7 IE8(Q) 中行内元素后相邻的浮动元素在某些情况下会折行放置在之前行内元素所在行框的底部 标准参考 W3C CSS 2.1 规范文档里对于浮动元素与非浮动行内元素相邻 ...

  6. “display:block-inline形式的Span或Div中添加文字后,导致Span或Div排版掉落、错位”的原因及解决方法

    最近在使用3个span(或div)制作带圆角边框的按钮时,按照常识,把span的display设置成inline-block,这样就可以设置span的width和height了,很爽的~ 可是当我在中 ...

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

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

  8. 读bootstrap2.3.2有感1

    起步: 下载编译好的bootstrap2文件,百度新版jquery.js,并复制html模版(hello world)放置在同一目录,然后看了下官网上的范例网站,心里还是很激动啊~ <!DOCT ...

  9. [bootstrap] 栅格系统和布局

    1.简介 栅格系统(grid systems),也称为“网格系统”,运用固定的格子设计版面布局,风格工整简洁.是从平面栅格系统演变而来. Bootstrap建立在12列栅格系统.布局.组件之上.以规则 ...

  10. Boostrap栅格系统

    Boostrap排版.链接样式设置了基本的全局样式.分别是:为body元素设置 布局容器:Bootstrap需要为页面内容和栅格系统包裹一个:container容器.Bootstrap提供了两个作此用 ...

随机推荐

  1. 智慧矿山IT智能运维自动化解决方案

    矿山企业是国民经济中的重要组成部分,其资源开发和产业链条中涉及的环节与过程非常繁琐和复杂.随着我国矿山企业规模逐年扩大,为了提高其生产效率和降低其生产成本,信息化.数字化建设成为当下矿山企业发展的重要 ...

  2. 理解 Vue 的 setup 应用程序钩子

    title: 理解 Vue 的 setup 应用程序钩子 date: 2024/9/30 updated: 2024/9/30 author: cmdragon excerpt: 摘要:本文详细介绍了 ...

  3. 画布canvas基础 01

    1. 什么是canvas canvas是用来绘制图形的.它可以用于动画.游戏画面.数据可视化.图片编辑以及实时视频处理等方面. <canvas width="500" hei ...

  4. 深入解析Spring AI框架:在Java应用中实现智能化交互的关键

    今天我们的Spring AI源码分析主题即将结束.我已经对自己感兴趣的基本内容进行了全面的审视,并将这些分析分享给大家.如果你对这个主题感兴趣,可以阅读以下几篇文章.每篇文章都层层递进,深入探讨相关内 ...

  5. Vmware挂载san存储_vSphere 6.x 共享存储LUN丢失分区表修复(精华)

    Vmware挂载san存储_vSphere 6.x 共享存储LUN丢失分区表修复 炎炎夏夜客户机房空调意外故障,前端ESXI物理服务器由于温度过高都自保关机,存储和SAN没有自保关机.上班修复空调后, ...

  6. 云原生爱好者周刊:KubeKey v2.1.0 alpha 版发布!

    KubeKey v2.1.0-alpha.0 发布啦!该版本的主要特性: 支持三种使用场景的 Etcd 集群(二进制部署,Kubeadm 部署,连接外置已存在的 Etcd 集群). 支持部署 Cont ...

  7. Python实现回数

    题目:回数是指从左向右读和从右向左读都是一样的数,例如 12321,909.请利用 filter()滤掉非回数: 思路:要实现回数判断,主要是将输入的数找到其各个位置的值,然后判断前后相对应的位置是否 ...

  8. CTime类缺陷

    如果构造CTime的时间不在下面这个范围内,会抛出异常

  9. 微积分 Calculus

    前言 如果你的工作中没有用到微积分,毫无疑问,你的工作是简单而枯燥的. 0 limit Say there is a function \(f(x) = x\). \(x \rightarrow a\ ...

  10. MaekLogic笔记 _001 _CURD_20210826

    MaekLogic笔记 _001 _CURD _20210826 1.插入文档 API xdmp:document-insert( $uri as xs:string, $root as node() ...