C# Span 源码解读和应用实践

一:背景

1. 讲故事

这两天工作上太忙没有及时持续的文章产出,和大家说声抱歉,前几天群里一个朋友在问什么时候可以产出 Span 的下一篇,哈哈,这就来啦!读过上一篇的朋友应该都知道 Span 统一了 .NET 程序 栈 + 托管 + 非托管 实现了三大块内存的统一访问,????????,而且在 .net 底层 Library 中也是一等公民的存在,很多现有的类都提供了对 Span / ReadOnlySpan 的支持。

  • String 对 Span / ReadOnlySpan 的支持

public sealed class String{[MethodImpl(MethodImplOptions.InternalCall)][NullableContext(0)]public extern String(ReadOnlySpan<char> value);}
  • StringBuilder 对 Span / ReadOnlySpan 的支持

public sealed class StringBuilder : ISerializable{public unsafe StringBuilder Append(ReadOnlySpan<char> value){if (value.Length > 0){fixed (char* value2 = &MemoryMarshal.GetReference(value)){Append(value2, value.Length);}}return this;}}
  • Int 对 Span / ReadOnlySpan 的支持

public readonly struct Int32{public static int Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null){NumberFormatInfo.ValidateParseStyleInteger(style);return Number.ParseInt32(s, style, NumberFormatInfo.GetInstance(provider));}}

怎么样,这些通用 & 基础的类都在大力对接 Span / ReadOnlySpan,更别说复杂类型了,其地位不言自明哈,接下来我们就从 Span 本身的机制聊起。

二:Span 原理探究

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 指定,是不是像极了指针????????????,是的,语言团队要保证你的程序高性能,还得照护你的人身安全,出了各种手段,真是煞费苦心!????????????

2. Span 用户态分析

虽然图已经画了,但还是有很多朋友希望眼见为实,必须实操演练,嘿嘿,无惧任何挑战,那我先把上面的图化成代码:

static void Main(string[] args){var nums = new int[] { 1, 2, 3, 4, 5, 6 };var span = new Span<int>(nums);Console.ReadLine();}

接下来我用 windbg 把线程栈中的 span 也找出来。


0:000> !clrstack -l
OS Thread Id: 0x181c (0)Child SP               IP Call Site
000000963277E5D0 00007ffc3e601434 ConsoleApp1.Program.Main(System.String[]) [E:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 13]LOCALS:0x000000963277E618 = 0x000001e956b8ab100x000000963277E608 = 0x000001e956b8ab20

从最后一行代码可以看出:span 的栈地址是 0x000000963277E608,栈内容是:0x000001e956b8ab20,按照图的理论:0x000001e956b8ab20 应该是 nums 数组元素 1 的内存地址,可以用 dp 验证一下。


0:000> dp 0x000001e956b8ab20
000001e9`56b8ab20  00000002`00000001 00000004`00000003
000001e9`56b8ab30  00000006`00000005 00000000`00000000
000001e9`56b8ab40  00007ffc`3e6c4388 00000000`00000000

从上面三行内存地址来看,数组的:1,2,3,4,5,6 依次排列,有些朋友可能有点小疑问,为啥 nums 的内存地址不是指向数组元素 1 的呢?那我来普及一下吧,先用 dp 唤出数组的内存地址。


0:000> dp 0x000001e956b8ab10
000001e9`56b8ab10  00007ffc`3e69f090 00000000`00000006
000001e9`56b8ab20  00000002`00000001 00000004`00000003
000001e9`56b8ab30  00000006`00000005 00000000`00000000

可以看出,第一排为: 00007ffc3e69f090 0000000000000006, 前面的 8 byte 表示 数组 的 方法表地址,后面的 8byte 表示 6 ,也就是说数组有 6个元素,不信的话我截一张图:

span 是由 _pointer + length 组成的,刚才的 _pointer 也给大家演示了,那 length 的值在哪里呢?因为 span 是 struct,所以需要用 dp 把刚才的线程栈最小的栈地址打出来就可以了。

到这里,我觉得我讲的已经够清楚了,如果还有点懵的话可以仔细想一想哈。

三: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 一定大有可为,大家赶紧学起来,????????????

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306965.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[C#.NET 拾遗补漏]12:死锁和活锁的发生及避免

多线程编程时&#xff0c;如果涉及同时读写共享数据&#xff0c;就要格外小心。如果共享数据是独占资源&#xff0c;则要对共享数据的读写进行排它访问&#xff0c;最简单的方式就是加锁。锁也不能随便用&#xff0c;否则可能会造成死锁和活锁。本文将通过示例详细讲解死锁和活…

64岁Python之父加入微软 | 谁说大龄程序员无出路

喜欢就关注我们吧&#xff01;现年 64 岁的 Python 创始人 Guido van Rossum 退休一年后再度复出&#xff0c;今天宣布已加入微软开发者部门 (Developer Division).我觉得退休生活乏味又无趣&#xff0c;因此已加入微软开发者部门。做什么工作&#xff1f;选择太多了&#xff0…

JAVA中的GridView每一个赋值,在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据...

导言&#xff1a;在前面的教程&#xff0c;我们对数据访问层进行扩展以支持数据库事务.数据库事务确保一系列的操作要么都成功&#xff0c;要么都失败。本文我们将注意力转到创建一个批更新数据界面.在本文&#xff0c;我们将创建一个GridView控件&#xff0c;里面的每一行记录…

微软发布VS Code Jupyter插件!不止Python!多语言的Jupyter Notebook支持来了!

北京时间 2020 年 11 月 12 日&#xff0c;微软发布了全新的 VS Code Jupyter 插件&#xff01;Jupyter 插件将 Jupyter Notebook 的功能引入 VS Code&#xff0c;并且将会支持更多语言和使用场景。Jupyter Notebook 支持创建和共享包含代码、方程式、文本和可视化内容的文档&a…

windows安全模式_鲁大师正式挂牌上市,使用鲁大师如何开启笔记本电脑全面节能模式...

10月10日消息&#xff0c;今天360旗下的鲁大师正式挂牌上市。上市之后&#xff0c;鲁大师的盘中涨幅一度扩大至100%&#xff0c;鲁大师的市值也一度达到了14亿港元。过去三个财年&#xff0c;鲁大师的营业收入分别为6981.2万、1.23亿和3.20亿人民币。简单介绍360&#xff0c;36…

跟我一起学Redis之Redis事务简单了解一下

前言关系数据库中的事务&#xff0c;小伙伴们应该是不陌生了&#xff0c;不管是在开发还是在面试过程中&#xff0c;总有两个问题逃不掉&#xff1a;•说说事务的特性&#xff1b;•事务隔离级别是怎么一回事&#xff1f;事务处理不好&#xff0c;数据就可能不准确&#xff0c;…

groovy 字符串截取最后一个_Python入门高级教程--Python 字符串

Python 字符串字符串是 Python 中最常用的数据类型。我们可以使用引号(或")来创建字符串。创建字符串很简单&#xff0c;只要为变量分配一个值即可。例如&#xff1a;var1 Hello World!var2 "Python Runoob"Python 访问字符串中的值Python 不支持单字符类型&a…

java面试题_阿里大厂流出的数百道 Java 经典面试题

BAT 常问的 Java基础39道常见面试题1.八种基本数据类型的大小&#xff0c;以及他们的封装类2.引用数据类型3.Switch能否用string做参数4.equals与的区别5.自动装箱&#xff0c;常量池6.Object有哪些公用方法7.Java的四种引用&#xff0c;强弱软虚&#xff0c;用到的场景8.Hashc…

​被冷落的运算符重载

基本类型可以使用运算符进行运算、比较、取反等操作。如果想使用运算符操作两个对象&#xff0c;我们就需要用到运算符重载。我们先看个例子&#xff0c;假如有个房子类&#xff0c;有长和宽两个属性。代码如下&#xff1a;接下来我们使用House类实例化两个对象&#xff1a;hou…

neo4j 查询同一节点的两个上级_WhatRoute for Mac(互联网流量诊断查询工具)

如果您想在不使用命令行的情况下执行流量诊断查询&#xff0c;那么WhatRoute是一个不错的选择。WhatRoute提供了一个干净且有条理的界面&#xff0c;主要提供Traceroute功能&#xff0c;但也可以执行Ping&#xff0c;域名服务查询&#xff0c;Whois查询以及监控进出计算机的流量…

快来参加学习.NET 挑战赛

今天访问dot.net 网站看到了一个学习.NET 挑战赛&#xff0c;发现已经赛程过半了&#xff0c;这是一个为那些想更多地了解 C# 和 .NET 的人举办的一个完全免费的课程活动&#xff0c;这些模块必须在 11 月底前完成。参加这个挑战赛&#xff0c;你必须从 .NET 学习挑战页面进入进…

excel怎么设置打印区域_别再浪费打印纸了!这样设置,Excel表格再大都能打印成一页!...

在打印Excel表格时&#xff0c;你是不是经常碰到过这种情况&#xff1a;明明排版好的表格&#xff0c;结果打印完却发现只显示一半......表格太宽导致无法打印在A4纸上......今天叨叨君就来分享几个有效的解决方法&#xff0c;教你轻轻松松将表格打印在一页纸上&#xff0c;一起…

xcode 修改 infodictionary_安卓系统修改复位键生效时间

文档说明本文档以SC806-CN-00(msm8909平台&#xff0c;Android 7)为例&#xff0c;说明如何修改复位键生效时间。应用背景默认情况下&#xff0c;按复位键&#xff0c;系统马上直接关机。在实际应用中&#xff0c;有可能由于干扰造成的抖动导致误关机行为。 为避免发生这种情况…

为什么曾经优秀的人突然变得平庸?

职场&认知洞察 丨 作者 / findyi这是findyi公众号分享的第95篇原创文章一个读者的提问&#xff1a;洋哥&#xff0c;我从小都是学霸&#xff0c;本硕都是985&#xff0c;计算机科班出身&#xff0c;但进入职场后却始终无法取得突破。工作5年还是基层员工&#xff0c;我该怎…

java编程_Java编程和C语言的比较

很多人都拿Java编程和c语言相比较&#xff0c;那么今天小编就来先说说个人理解吧&#xff0c;新手学习Java很简单&#xff0c;上手也很容易&#xff0c;只需要会拼音就可以&#xff0c;简单而且没有门槛&#xff0c;而c语言学习成本高&#xff0c;更需要投入较大的精力&#xf…

.NET Core3.1升级.NET5,坑还真不少...

11月11号是电商狂欢的日子&#xff0c;也是.NET5正式发布的日子&#xff0c;媳妇儿等着零点秒杀&#xff0c;我却在刷新着微软官网等更新&#xff0c;然后第一时间开始折腾。此前Scott Hunter在博客信誓旦旦.NET Core3.1平滑迁移.NET5&#xff0c;于是当天就去升级我的宝藏项目…

requestPermissions读写手机存储权限_泛圈云盘可为企业建立高效安全的云办公在线协同文档存储?...

泛圈企业云盘结合智能手机和无线网络&#xff0c;实现对任何办公地点和办公时间的无缝访问&#xff0c;提高办公效率。它可以连接客户原有的各种IT系统&#xff0c;包括OA、邮件、ERP等各种个人业务系统&#xff0c;使手机也可以用来操作、浏览、管理公司的所有工作事务&#x…

怎么将SVG转成PNG(.NET工具包编写)

序一天&#xff0c;作者在深圳湾吹风时突然想到自己还有 20 多位粉丝&#xff0c;所以决定每周至少要水一篇文章。众所周知&#xff0c;一篇文章要有封面&#xff0c;正痛苦时&#xff0c;.NET 官方网站更新了一大波质量上乘的插图&#xff1b;高兴之余&#xff0c;发觉平台不支…

ghelper怎么在手机上用_当长时间不用手机玩《崩坏3》、《战双》

崩坏3、战双&#xff0c;虽然是手机游戏&#xff0c;作为一个PC党&#xff0c;我还是热衷于用电脑玩游戏。用电脑玩游戏可以把画质全部开到最大&#xff0c;依旧可以顺畅地进行游戏。手机就不行&#xff0c;即使是苹果&#xff0c;把画质拉满&#xff0c;依旧会有卡顿。特别是崩…

System.Text.Json中时间格式化

转自&#xff1a;Rayomcnblogs.com/Rayom/p/13967415.html简介.Net Core 3.0开始全新推出了一个名为System.Text.Json的Json解析库&#xff0c;用于序列化和反序列化Json&#xff0c;此库的设计是为了取代Json.Net(Newtonsoft.Json)时间格式化的不足System.Text.Json的优点就不…