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

Posted DotNet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# Span 源码解读和应用实践相关的知识,希望对你有一定的参考价值。

(给DotNet加星标,提升.Net技能

转自:一线码农
cnblogs.com/huangxincheng/p/13974476.html

一、背景


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 两个实例字段,不知道看完这两个字段脑子里是不是有一幅图,大概是这样的。




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: et5ConsoleApp2ConsoleApp1Program.cs @ 13]
LOCALS:
0x000000963277E618 = 0x000001e956b8ab10
0x000000963277E608 = 0x000001e956b8ab20



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



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



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



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


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


三、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();
}


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


结果是很轻松的算出来了,但你仔细想想这里是不是有点什么问题,比如说为了从 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 个系统字符串,性能也得到了不小的提升,

以上是关于C# Span 源码解读和应用实践的主要内容,如果未能解决你的问题,请参考以下文章

源码解读之工具--Source Insight

Docker网络详解及pipework源码解读与实践

#yyds干货盘点# mybatis源码解读:executor包(语句处理功能)

Jquery源码解读

[3D游戏开发实践] Cocos Cyberpunk 源码解读-高中低端机性能适配策略

手撸Redis分布式锁(8个版本的渐进式源码实践解读)