前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【5min+】闪电光速拳? .NetCore 中的Span

【5min+】闪电光速拳? .NetCore 中的Span

作者头像
句幽
发布2020-04-27 16:11:25
5430
发布2020-04-27 16:11:25
举报
文章被收录于专栏:一起玩转.NET一起玩转.NET

系列介绍

简介

【五分钟的DotNet】是一个利用您的碎片化时间来学习和丰富.net知识的博文系列。它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net知识等等。

5min+不是超过5分钟的意思,"+"是知识的增加。so,它是让您花费5分钟以下的时间来提升您的知识储备量。

正文

在dotnet core2.x之后,引入了一个叫做Span<T>的类型。如果您的项目已经升级到了新版的dotnet core 以及使用C# 7+。您会发现我们曾经使用的许许多多类型都增加了一个扩展方法“AsSpan()”。在Vs中小手一点就会出现:

var s = ("xxx").AsSpan();
var s1 = new byte[10].AsSpan();
//.......more

那么这个家伙到底是个什么东西?怎么用呢?

先来扒一扒它的内部方法:

public readonly ref struct Span<T>
{
    public void Clear();
    public void CopyTo([NullableAttribute(new[] { 0, 1 })] Span<T> destination);
    public void Fill(T value);
    public Enumerator GetEnumerator();
    public Span<T> Slice(int start, int length);
    public T[] ToArray();
    public override string ToString();

    //.....
}

这里只展示它部分的方法,但是关键的一点我们可以看到:它是一个结构性(struct 关键字)。

而且!!而且!!! 你没看错,它还加了一个ref关键字。

所以按照我们在上一篇文章中介绍过的 .net中的栈和堆,我们猜想这种结构类型的数据应该是存放在内存栈中,具有很快的访问速度。而且它拥有了ref关键字,证明它具有ref结构体的特点:

  • 不能对 ref struct 装箱
  • ref struct 类型不能实现接口
  • 不能将 ref struct 声明为类或常规结构的字段成员
  • 不能声明异步方法中属于 ref struct 类型的本地变量
  • 无法在迭代器中声明 ref struct 本地变量
  • 无法捕获 Lambda 表达式或本地函数中的 ref struct 变量

而且根据它公开的这些方法,我们会发现它有点类似我们常用的几个基础类型:string 、 byte[] ……。

所以直觉告诉我们,它应该是一个拿来存放数据的类型。

so,来看看MSDN - Magazine中它的解释:

System.Span<T> 是在 .NET 中发挥关键作用的新值类型。使用它,可以表示任意内存的相邻区域,无论相应内存是与托管对象相关联,还是通过互操作由本机代码提供,亦或是位于堆栈上。除了具有上述用途外,它仍能确保安全访问和高性能特性,就像数组一样。

果不其然,和我们猜想的一样。那么它出现的意义是什么呢? 性能!!!!

而且是超级快的性能。大家都知道以往如果我们想提高数据间的操作效率(比如数据偏移、裁剪等),就只能使用指针来操作内存中的数据。这样虽然一波操作猛如虎,但是写起来费劲不说,我们还得将传统的C#代码设置为不安全代码,除了添加unsafe关键字之外还需要打开项目中执行不安全代码的选项。

所以,有没有办法既不操作指针而又有高性能呢? 好吧,Span大爷来了。

Span在C# 7.x中被引入,所以它的年龄还算比较小,也是因为这些原因。以往的项目可能没有办法使用它。

它到底有多快

大家一般都是想直接看东西,所以我写了一份对比的代码。功能很简单,都是截取字符串中的一部分代码,并且进行多次的循环操作。

执行结果我都惊呆了:

是的,您没有看错。差距不是一般的大。

其实刚开始我以为Span并没有什么作用,因为我将数据源(图中的compareStr)仅仅设置为了几个单词。然后对他们进行了1亿的循环操作,但是最后的结果只有很小的差距,不到百分之30。

后来我想了一下,应该让数据更贴近现实,于是就将一张图片转换为base64然后作为数据源。结果惊呆了,差了接近百倍。而且随着循环次数和对数据源的操作次数的增多,Span和传统字符串之间的性能差距更大。

传说中的闪电光速拳到底有多快呢

它为什么这么快

它与传统的string操作比起来为什么会具有这么快的速度呢? 按照我们之前的一些猜想和msdn所给出的一点信息,我们可以得到以下的结论:

  • 它分配堆栈上而不是在托管堆。
  • 它所创建的数据是内存连续的,因此具有更快的遍历速度。

这些特点和string等原有类型比起来就非常的具有优势了:原来对string操作涉及到大量的字符串分配和内存复制。所以当操作的数据量小的时候还好,但是随着操作次数和处理数据量的增加之后,这是非常消耗性能的。

Span会给我们带来什么

那么,既然它拥有如此高的性能,那么我们该在什么地方使用它呢?

这很简单,如果您以前有对大量字符串进行截取或者处理的地方,一般都可以替换为Span。(为什么是一般呢?)

除了string可以转换为span之外,其它的byte[],char[]等等都可以转换为span进行操作。所以这是非常值得高兴的一件事情,它会为我们数据处理带来显著的性能提升。比如字节流缓冲,视频流的处理,数据的加密解密等等操作都可以使用Span来完成了。

so,在现在的.NETCore runtime中,您会发现大量的类中都开始使用了Span。

而且,Span为我们实现了ExplicitImplicit,所以我们可以直接将支持的数组类型赋值给Span: (如果您不了解这两个关键字:戳这儿)

var arr = new byte[10];
Span<byte> bytes = arr; // 直接将byte[]赋值给Span

心动了吗?了解以下Span,并且尝试着使用它吧。

但是,请注意!! Span也是具有缺点的:因为只能存放在内存栈中,所以它不具有线程安全,它无法跨异步操作。还有它ref结构的原因,无法装箱拆箱等。

那么如果我们需要跨线程共享数据,又想拥有高性能怎么办呢? 别急,下一期咱们再来谈。?

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-01-17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 系列介绍
    • 简介
    • 正文
      • 它到底有多快
        • 它为什么这么快
        • Span会给我们带来什么
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档