Span 原理探究

ref结构

ref struct是仅在堆栈上的值类型:

  • 表现一个顺序结构的布局;(译注:可以理解为连续内存)
  • 只能在堆栈上使用。即用作方法参数和局部变量;
  • 不能是类或正常结构的静态或实例成员;
  • 不能是异步方法或lambda表达式的方法参数;
  • 不能动态绑定、装箱、拆箱、包装或转换。

ref struct也被称为嵌入式引用。

1. Span 源码分析

灵活运用 Span 解决工作中的实际问题我相信大家应该没什么毛病了,有了这个基础再从 Span 的源码 和 用户态 和大家一起深度剖析,从源码开始吧。


public readonly ref struct Span<T>
{
internal readonly ByReference<T> _pointer; private readonly int _length;
}

上面代码的 ref struct 可以看出,这个 Span 是只可以分配在栈上的值类型,然后就是里面的 _pointer 和 _length 两个实例字段,不知道看完这两个字段脑子里是不是有一幅图,大概是这样的。

可以清晰的看出,Span 就是用来映射一段可以连续访问的内存地址,空间大小由 length 控制,开始位置由 _pointer 指定,是不是像极了指针,是的,语言团队要保证你的程序高性能,还得照护你的人身安全,出了各种手段,真是煞费苦心!

二、Span<T>常规用法

    static void Main()
{ var nums = new int[] { 1, 2, 3, 4, 5, 6,8,9,7,10,11,12,13,14,15,16 };
//用数组来实例化span
var span = new Span<int>(nums); var Slice1 = span.Slice(1, 4);//2 3 4 5
var Slice2= span.Slice(10);// 11,12,13,14,15,16
var Slice3= Slice1.ToArray();//转化成数组
var Slice4 = new Span<int>(new int[Slice1.Length]);//新建一个span,长度不能小于slice1 要不会报错
Slice1.CopyTo(sp);
foreach (var item in Slice2)
{
Console.WriteLine($"{item}");
} }

三:Span 在 String 和 List 的实践

Span的应用场景真的是太多了,不可能在这篇一一列举,这里我就举两个例子吧,让大家能够感受到 Span 的强大即可。

1. 在 String 上的应用

案例:如何高效的计算出用户输入的值 10+20 ?

1) 传统 Substring 做法

传统的做法很简单,截取呗,代码如下:


static void Main(string[] args)
{
var word = "10+20"; var splitIndex = word.IndexOf("+"); var num1 = int.Parse(word.Substring(0, splitIndex)); var num2 = int.Parse(word.Substring(splitIndex + 1)); var sum = num1 + num2; Console.WriteLine($"{num1}+{num2}={sum}"); Console.ReadLine();
}

结果是很轻松的算出来了,但你仔细想想这里是不是有点什么问题,比如说为了从 word 中扣出 num,我用了两次 SubString,就意味着会在 托管堆 上生成两个 string,如果说我执行 1w 次话,那托管堆上会不会有 2w 个 string 呢? 修改代码如下:


for (int i = 0; i < 10000; i++)
{
var num1 = int.Parse(word.Substring(0, splitIndex)); var num2 = int.Parse(word.Substring(splitIndex + 1)); var sum = num1 + num2;
}

然后看一下 托管堆 上 String 的个数


0:000> !dumpheap -type String -stat
Statistics:
MT Count TotalSize Class Name
00007ffc53a81e18 20167 556538 System.String

托管堆上有 20167 个,挺恐怖的,真的是给 GC 添麻烦哈,这里还有 167 个是系统自带的,接下来的问题是有没有办法替换 SubString 从而不生成临时string呢?

2) 新式 Span 做法

如果看懂了 Span 结构图,你就应该会使用 _pointer + length 将 string 进行切片处理,对不对,代码如下:


for (int i = 0; i < 10000; i++)
{
var num1 = int.Parse(word.AsSpan(0, splitIndex)); var num2 = int.Parse(word.AsSpan(splitIndex)); var sum = num1 + num2;
}

然后在 托管堆 验证一下,是不是没有 临时 string 了?


0:000> !dumpheap -type String -stat
Statistics:
MT Count TotalSize Class Name
00007ffc53a51e18 167 36538 System.String

可以看到就只有 167 个系统字符串,性能也得到了不小的提升,。

2. 在 List 上的应用

平时用 Span 的时候,更多的会应用到 Array 上面,毕竟 Array 在托管堆上是连续内存,方便 Span 在上面画一个可视窗口,其实不仅仅是 Array,从 .NET5 开始在 List 上画一个视图也是可以的,截图如下:

因为 List 的 CURD 会导致底层的 Array 忽长忽短或重新分配,也就无法实现物理上的连续内存,所以 Span 应用到 List 之后,希望List是不可变的,这也是官方的建议。

四:总结

总的来说,Span 在 .NET 底层框架中的地位是越来越显著了,相信 netCore 追求更高更快的性能上 Span 一定大有可为,大家赶紧学起来,

原文链接:https://www.cnblogs.com/huangxincheng/p/13974476.html

【C# IO 操作 】Span<T>类的更多相关文章

  1. java的IO操作:System类对IO的支持。

    目标: 1,掌握SYStem对IO的三种支持: system.out system.in system.err 2,掌握system.out及system.err的区别. 3,掌握输入,输出重定向. ...

  2. XML序列化 判断是否是手机 字符操作普通帮助类 验证数据帮助类 IO帮助类 c# Lambda操作类封装 C# -- 使用反射(Reflect)获取dll文件中的类型并调用方法 C# -- 文件的压缩与解压(GZipStream)

    XML序列化   #region 序列化 /// <summary> /// XML序列化 /// </summary> /// <param name="ob ...

  3. Java的IO操作---File类

    目标 1)掌握File类作用 2)可以使用file类中方法对文件进行读写操作. File类 唯一与文件有关的类.使用file类可进行创建或删除操作,要想使用File类,首先观察File类的构造方法. ...

  4. C++ IO操作API及注意事项(包含一个日志类的实现)

    C++是一个抽象程度比C高很多的语言,在使用C++时,编译器做了很多工作,如果我们不对C++的某些特性的实现机制进行了解,那么编程时也许会有很多疑惑,我们也许知道怎样做才是正确的,但不知道为什么要这样 ...

  5. [19/04/04-星期四] IO技术_CommonsIO(通用IO,别人造的轮子,FileUtils类 操作文件 & IOUtilsl类 操作里边的内容 )

    一.概念 JDK中提供的文件操作相关的类,但是功能都非常基础,进行复杂操作时需要做大量编程工作.实际开发中,往往需要 你自己动手编写相关的代码,尤其在遍历目录文件时,经常用到递归,非常繁琐. Apac ...

  6. C# IO操作(二)File类和Directory类的常用方法

    本篇主要介绍一些常用的IO操作,对文件和目录的操作:留给自己复习之用. 1.创建文件 string sPath1=Path.GetDirectoryName(Assembly.GetExecuting ...

  7. [.NET] 利用 async & await 进行异步 IO 操作

    利用 async & await 进行异步 IO 操作 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6082673.html  序 上次,博主 ...

  8. 文件IO操作..修改文件的只读属性

    文件的IO操作..很多同行的IO工具类都是直接写..但是如果文件有只读属性的话..则会写入失败..所以附加了一个只读的判断和修改.. 代码如下: /// <summary> /// 创建文 ...

  9. python之协程与IO操作

    协程 协程,又称微线程,纤程.英文名Coroutine. 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B ...

  10. JAVASE02-Unit08: 文本数据IO操作 、 异常处理

    Unit08: 文本数据IO操作 . 异常处理 * java.io.ObjectOutputStream * 对象输出流,作用是进行对象序列化 package day08; import java.i ...

随机推荐

  1. VUE3 之 组件传参

    1. 概述 韦奇定律告诉我们:大部分人都很容易被别人的话所左右,从而开始动摇.怀疑,最终迷失自我.因此我们要努力的坚定信念,相信自己,才不会被周围的环境所左右,才能取得最终的胜利. 言归正传,之前我们 ...

  2. Spring Boot 3.0.0 发布第一个里程碑版本M1,你的 Java 升到17 了吗?

    2022年1月20日,Spring官方发布了Spring Boot 3.0.0的第一个里程碑版本M1. 下面一起来来看看Spring Boot 3.0.0 M1版本都有哪些重大变化: Java基线从 ...

  3. Shell 脚本循环遍历日志文件中的值进行求和并计算平均值,最大值和最小值

    本文为博主原创,转载请注明出处: 最近在进行压测,为了观察并定位服务性能的瓶颈,所以在代码中很多地方加了执行耗时的日志,但这种方式只能观察,却在压测的时候,不太能准确的把握代码中某些方法的性能,所以想 ...

  4. 聊一聊如何用C#轻松完成一个SAGA分布式事务

    背景 银行跨行转账业务是一个典型分布式事务场景,假设 A 需要跨行转账给 B,那么就涉及两个银行的数据,无法通过一个数据库的本地事务保证转账的 ACID ,只能够通过分布式事务来解决. 市面上使用比较 ...

  5. python内置re模块全面实战

    目录 一:取消转义 二:python内置模块之re模块 三:常用方法 findall search match 简便 四:常用方法 finditer 匹配文件多情况 五:切割 替换 内置模块 六:分组 ...

  6. 学习Java第9天

    今天所作的工作: 反射,枚举类型与泛型 明天工作: 1.线程 2.网络通信 所遇到的问题及解决方法: 反射基本思想,泛型类似于类模板. 理解反射太难了,转悠了好半天,关键是理解反射的思想,才容易学.

  7. plsql 数据库事件触发器

    --4.数据库事件触发器 需要超管的权限 /* 数据库事件触发器有数据库级和模式级两种. 前者定义在整个数据库上,触发事件是数据库事件,如数据库的启动.关闭,对数据库的登录或退出. 后者定义在模式上, ...

  8. HBase安装教程

    一.版本介绍 linux : CentOS7 Hadoop : 2.7.6 zookeeper : 3.4.6 hbase : 1.4.6 jdk : jdk1.8.0_171 三个节点的主机名分别为 ...

  9. 自定义CALayer

    1.如何自定义Layer.     自定义CALayer的方式创建UIView的方式非常相似.      CALayer *layer = [CALayer layer];      layer.fr ...

  10. springboot实现分布式锁(spring integration,redis)

    Springboot实现分布式锁(Spring Integration+Redis) 一.在项目的pom.xml中添加相关依赖 1)Spring Integration依赖 <dependenc ...