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. php获取支付宝用户信息

    php获取支付宝用户信息 一:创建应用 要在您的应用中使用支付宝开放产品的接口能力: 您需要先去蚂蚁金服开放平台(open.alipay.com),在开发者中心创建登记您的应用,此时您将获得应用唯一标 ...

  2. Android平台下的cpu利用率优化实现

    目录 背景 CPU调频 概念 实现 验证 线程CPU亲和性 概念 亲和性控制 API 应用层控制实现 验证 线程优先级 概念 实现 验证 背景 为了进一步优化APP性能,最近针对如何提高应用对CPU的 ...

  3. 一些git使用命令,误删本地分支,如何找回

    1.新建仓库 git init 2.如果要本地更改文件,需要更新到远端. git status # 查看当前文件状态 git add -A # "git add -A" 命令用于将 ...

  4. Excel读写之xlrd模块

    1.1.xlrd模块介绍 xlrd:用于读取Excle数据文件将返回的数据对象放到内存中,然后查询数据文件对象的相关信息. xlwt:用于在内存中生成新的数据文件对象,处理完成后写入到Excel数据文 ...

  5. iOS解决嵌套在ScrollView中的TableView滑动手势冲突问题

    在项目开发中用到了这种需求,顶部两个选项卡,底部列表有侧滑手势,布局是就不说了,遇到的问题是左滑的时候添加滑动手势效果.一直纠结了很久,后来想到可能是手势冲突的问题. 解决办法开启多手势代理即可,我是 ...

  6. 云原生周刊:Artifact Hub 成为 CNCF 孵化项目|2024.9.23

    开源项目推荐 Coroot Coroot 是一个开源监控工具,旨在为云原生应用提供可观察性.它通过整合指标.日志和追踪信息,专注于提供应用性能的洞察. DirectPV DirectPV 是一个开源项 ...

  7. 云原生周刊:Kubernetes 1.29 中的删除、弃用和主要更改 | 2023.11.27

    开源项目推荐 Orphaned ConfigMaps 该版本库包含一个脚本,用于识别 Kubernetes 命名空间中的孤立的配置映射.孤立的配置映射是指那些未被命名空间中的任何活动 Pod 或容器引 ...

  8. 快速部署kafka集群和可视化界面

    3台机器部署kafka集群的分布 10.1.161.111 -> kafka(1个节点)+ zookeeper(一个节点)+ kowl(一个节点) 10.1.161.112 -> kafk ...

  9. 学习JavaScript第三天

    文章目录 1.数组 1.1创建数组 1.2数组方法 1.3数组遍历 2.函数 2.1函数的定义 2.2函数的参数以及返回值 2.3函数的作用域 2.4函数传参 3.对象 1.数组 在JavaScrip ...

  10. python bytecode解析

    python bytecode解析 前言 我们的电脑是怎么运行的呢?计算机内部的 CPU 处理器是个硅片,上面雕刻着精心布置的电路,输入特定的电流,就能得到另一种模式的电流,而且模式可以预测,给这些模 ...