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. foobar2000 v1.6.11 汉化版(更新于 2022.08.25)

    foobar2000 v1.6.11 汉化版 -----------------------[软件截图]---------------------- -----------------------[软 ...

  2. 【USB3.0协议学习】Topic3·三种Reset Events分析

    USB3.0中的三种Reset Events 1. PowerOn Reset PowerOn Reset被用来代指上电复位,当一个device接入到root hub或者外置hub的时候,该devic ...

  3. 主要将子文件下大量图片进行路径编号,并保存到csv文件当中。方便直接从文件读取图片路径以及其他图片信息

    # coding: utf-8 #主要将子文件下大量图片进行路径编号,并保存到csv文件当中.方便直接从文件读取图片路径以及其他图片信息. #我做的是图像分割,所以存在三类分割区域:["la ...

  4. Java实用小工具系列2---使用StopWatch统计多个任务耗时分布

    在Java中经常需要统计程序的使用时间,如果只是一个时间段统计比较好处理,可以直接使用System.currentTimeMillis().但如果一个程序中包含多个步骤,需要统计每个步骤耗时,并且需要 ...

  5. Java日期时间API系列14-----Jdk8中java.time包中的新的日期时间API类,java日期计算1,获取年月日时分秒等

    通过Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析 ,可以看出java8设计非常好,实现接口Temporal, Tempora ...

  6. Android复习(六)核心组件—>Activity 任何和返回栈、进程和应用生命周期、Parcelable和Bundle

    了解任务和返回堆栈 任务是用户在执行某项工作时与之互动的一系列 Activity 的集合.这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中.例如,电子邮件应用可能有一 ...

  7. 什么是AI网关?AI网关在企业系统中承担什么角色?

    AI 大模型的发展正在推动各行业的增长,据有关报告显示:"未来十年内预计年均增长率将达到37.3%,全球企业预计在2027年之前将在AI领域投入8000亿美元".这一迅猛发展促使许 ...

  8. 云原生爱好者周刊:开源替代品开始围剿 Docker Desktop

    云原生一周动态要闻: Docker 更新和扩展了产品订阅 NGINX Ingress Controller 1.0.0 发布 Tanzu 应用平台的公开测试版发布 IBM 开源 Tornjak Kub ...

  9. 题解:AT_abc370_c [ABC370C] Word Ladder

    题目传送门 luogu观看 简要题意 给两个序列 \(S\) 和 \(T\),输出的第一个数是它能改变的总个数,后面跟着的第 \(i\) 个是改变 \(i\) 个数之后,字典序最小的结果. 思路 当 ...

  10. 经验总结之 _DEBUGGER _01 _Invalid coercion null-node{} as xsstring _20210909

    经验总结之 _DEBUGGER _01 _Invalid coercion null-node{} as xsstring _20210909 今天喜提一个bug,报错情况如下: 该项目使用的是 sp ...