站在前人的肩膀上重新透视C# SpanT数据结构

34f2b5114f312ec615b55939f93bb936.gif

    先谈一下我对Span的看法, Span是指向任意连续内存空间的类型安全、内存安全的视图,可操作的滑动窗口。

Span和Memory都是包装了可以在pipeline上使用的结构化数据的内存缓冲器,他们被设计用于在pipeline中高效传递数据。

定语解读

这里面许多定语,值得我们细细揣摩:

  1. 1. 指向任意连续内存空间:支持托管堆,原生内存、堆栈, 这个可从Span

    的几个重载构造函数窥视一二。
  2. 2. 类型安全:Span 是一个泛型。

  3. 3. 内存安全: Span[1]是一个readonly ref struct数据结构,用于表征一段连续内存的关键属性被设置成只读readonly, 保证了所有的操作只能在这段内存内。

// 截取自Span源码 
public readonly ref struct Span<T>
{// 表征一段连续内存的关键属性 Pointer & Length 都只能从构造函数赋值/// <summary>A byref or a native ptr.</summary>internal readonly ByReference<T> _reference;/// <summary>The number of elements this Span contains.</summary>private readonly int _length;[MethodImpl(MethodImplOptions.AggressiveInlining)]public Span(T[]? array){if (array == null){this = default;return; // returns default}if (!typeof(T).IsValueType && array.GetType() != typeof(T[]))ThrowHelper.ThrowArrayTypeMismatchException();_reference = new ByReference<T>(ref MemoryMarshal.GetArrayDataReference(array));_length = array.Length;}
}
  1. 4. 视图:操作结果会直接体现到底层的连续内存。

    e9b1bca58c9347b6ac990a8bbae23f5e.png

至此我们来看一个简单的用法, 利用span操作指向一段堆栈空间。

static  void  Main(){Span<byte> arraySpan = stackalloc byte[100];  // 包含指针和Length的只读指针, 类似于go里面的切片byte data = 0;for (int ctr = 0; ctr < arraySpan.Length; ctr++)arraySpan[ctr] = data++;arraySpan.Fill(1);var arraySum = Sum(arraySpan);Console.WriteLine($"The sum is {arraySum}");   // 输出100arraySpan.Clear();var slice  =  arraySpan.Slice(0,50); // 因为是只读属性, 内部New Span<>(), 产生新的切片arraySum = Sum(slice);Console.WriteLine($"The sum is {arraySum}");  // 输出0}[MethodImpl(MethodImplOptions.AggressiveInlining)]static int  Sum(Span<byte> array){int arraySum = 0;foreach (var value in array)arraySum += value;return arraySum;}
  • • 此处Span  指向了特定的堆栈空间, Fill,Clear 等操作的效果直接体现到该段内存。

  • • 注意Slice切片方法,内部实质是产生新的Span,是一个新的视图,对新span的操作会体现到原始底层数据结构。

  • [MethodImpl(MethodImplOptions.AggressiveInlining)]public Span<T> Slice(int start){if ((uint)start > (uint)_length)ThrowHelper.ThrowArgumentOutOfRangeException();return new Span<T>(ref Unsafe.Add(ref _reference.Value, (nint)(uint)start /* force zero-extension */), _length - start);}
  •       从Slice切片源码可以看到,实质是利用原ptr & length 产生包含新的ptr & length的操作视图, ptr其实是指针的移动,也就是定位新的数据块, 但是终归是在原始数据块内部。 

衍生技能点

我们再细看Span的定义, 有几个关键词建议大家温故而知新。

1. readonly strcut[2]

从C#7.2开始,你可以将readonly作用在struct上,指示该struct不可改变

span 被定义为readonly struct,内部属性自然也是readonly,从上面的分析和实例看我们可以针对Span表征的特定连续内存空间做内容更新操作;
如果想限制更新该连续内存空间的内容, C#提供了ReadOnlySpan<T>类型, 该类型强调该块内存只读,也就是不存在Span 拥有的Fill,Clear等方法。

一线码农大佬写了文章讲述[使用span对字符串求和]的姿势,大家都说使用span能高效操作内存,我们对该用例BenchmarkDotNet压测。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Buffers;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;namespace ConsoleApp3
{public class Program{static  void Main(){var summary = BenchmarkRunner.Run<MemoryBenchmarkerDemo>();}}[MemoryDiagnoser,RankColumn]public class MemoryBenchmarkerDemo{int NumberOfItems = 100000;// 对字符串切割, 会产生字符串小对象[Benchmark]public void  StringSplit(){for (int i = 0; i < NumberOfItems; i++){var s = "97 3";var arr = s.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);var num1 = int.Parse(arr[0]);var num2 = int.Parse(arr[1]);_ = num1 + num2;}}// 对底层字符串切片[Benchmark]public void StringSlice(){for (int i = 0; i < NumberOfItems; i++){var s = "97 3";var position = s.IndexOf(' ');ReadOnlySpan<char> span = s.AsSpan();var num1 = int.Parse(span.Slice(0, position));var num2 = int.Parse(span.Slice(position));_= num1+ num2;}}}
}

a839de4b74e6907926d8386b15ec577b.png

压测解读:

 对字符串运行时切分,不会利用驻留池,于是case1会分配大量小对象;对gc造成压力。  

 case2对底层字符串切片,虽然会产生不同的透视对象Span, 但是实际引用了的原始内存块的偏移区间, 不存在分配新内存。

2. ref struct[3]

从C#7.2开始,ref可以作用在struct,指示该类型被分配在堆栈上,并且不能转义到托管堆

Span,ReadonlySpan 包装了对于任意连续内存快的透视操作,但是只能被存储堆栈上,不适用于一些场景,例如异步调用,.NET Core 2.1为此新增了Memory[4] , ReadOnlyMemory, 可以被存储在托管堆上,这个暂时按下不表。

最后用一张图总结, 本文成文,感谢[ yi念之间 ]大佬参与讨论。

5aeba6400eb7cb3df0c3c0211e7e3053.png

引用链接

[1] Span: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Span.cs
[2] readonly strcut: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#readonly-struct
[3] ref struct: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct
[4] Memory: https://docs.microsoft.com/en-us/dotnet/standard/memory-and-spans/memory-t-usage-guidelines

与本文相关的经典文章

C#语法糖系列 —— 第四篇:聊聊 Span 的底层玩法

非常简单的string驻留池,你对它真的了解吗

22b0140495a107bfa25e4a5e193e5d49.gif

年终总结:2021技术文大盘点  |  打包过去,面向未来

项目总结:麻雀虽小,五脏俱全

理念总结:实话实说:只会.NET,会让我们一直处于鄙视链、食物链的下游

云原生系列: 什么是云原生?

点“0331d040939fbc7a382ba5407a76f1f5.gif戳“在看cd7dc3fef1509c0d6af10a1ce66db3fe.gif

体现态度很有必要!

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

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

相关文章

《零基础看得懂的C语言入门教程 》——(十)C语言的指针原来是这样

一、学习目标 了解指针的概念了解指针的使用方法了解双重指针 目录 C语言真的很难吗&#xff1f;那是你没看这张图&#xff0c;化整为零轻松学习C语言。 第一篇&#xff1a;&#xff08;一&#xff09;脱离学习误区 第二篇&#xff1a;&#xff08;二&#xff09;C语言没那…

bigpipe提升网站响应速度

2019独角兽企业重金招聘Python工程师标准>>> 主要思想就是通过异步 发起一次请求&#xff0c;后端不关闭输出流&#xff0c;多个线程处理各自任务&#xff0c;然后分别发送到客户端。 https://github.com/4rnold/Demo-Project/tree/master/bigpipe-demohttps://gith…

《零基础看得懂的C语言入门教程 》——(十一)C语言自定义函数真的很简单

一、学习目标 了解C语言的自定义函数的使用方法了解C语言自定义函数的传参了解C语言自定义函数的返回值 目录 C语言真的很难吗&#xff1f;那是你没看这张图&#xff0c;化整为零轻松学习C语言。 第一篇&#xff1a;&#xff08;一&#xff09;脱离学习误区 第二篇&#xf…

**【ci框架】精通CodeIgniter框架

http://blog.csdn.net/yanhui_wei/article/details/25803945 一、大纲 [php] view plaincopy1、codeigniter框架的授课内容安排 2、codeigniter框架的简介 |-----关于框架的概念 |-----使用CI框架的好处 |-----为什么选择CI框架 3、codeigniter框架…

《假如编程是魔法之零基础看得懂的Python入门教程 》——(一)既然你选择了这系列教程那么我就要让你听得懂

一、前言 几个月前编写了一份python语言入门的博文&#xff0c;近期重新审阅了一遍发现编写的质量太过随意&#xff0c;可能对于一部分人并不是非常友好&#xff0c;故此重新编写Python语言的零基础教程。 本篇教程将会尽量把一些专业术语给读者讲解清楚&#xff0c;并且让读…

环形队列

在网上看到一篇比较好的介绍队列的文章&#xff0c;地址为&#xff1a;http://www.cnblogs.com/kubixuesheng/p/4104802.html 特此感谢原创作者&#xff0c;以下均为摘抄。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1、…

Blazor University (19)使用 RenderFragments 模板化组件 —— 数据传递

原文链接&#xff1a;https://blazor-university.com/templating-components-with-renderfragements/passing-data-to-a-renderfragement/将数据传递给 RenderFragment源代码[1]到目前为止&#xff0c;我们使用了仅包含子标记的 RenderFragments&#xff0c;然后在渲染组件时按…

《零基础看得懂的C语言入门教程 》——(十二)原来结构体是这么回事

一、学习目标 了解C语言的结构体的使用方法了解C语言结构体的结构的赋值了解多种C语言结构体变量的赋值方法和取值方法 目录 C语言真的很难吗&#xff1f;那是你没看这张图&#xff0c;化整为零轻松学习C语言。 第一篇&#xff1a;&#xff08;一&#xff09;脱离学习误区 第…

mysql关系数据库引擎_MySQL数据库引擎详解

作为Java程序员&#xff0c;MySQL数据库大家平时应该都没少使用吧&#xff0c;对MySQL数据库的引擎应该也有所了解&#xff0c;这篇文章就让我详细的说说MySQL数据库的Innodb和MyIASM两种引擎以及其索引结构。也来巩固一下自己对这块知识的掌握。Innodb引擎Innodb引擎提供了对数…

Java之synchronized的JVM底层实现原理精简理解

1 synchronized的JVM底层原理实现的精简理解 Java 虚拟机中的synchronized基于进入和退出Monitor对象&#xff08;也称为管程或监视器锁&#xff09;实现&#xff0c; 无论是显式同步(synchronized作用在同步代码块&#xff0c;有明确的 monitorenter 和 monitorexit 指令) 还是…

三分钟掌握Actor和CSP模型

点击上方蓝字进行关注前文传送门&#xff1a;《三分钟掌握共享内存模型和 Actor模型》&#xff0c; 一直想比较Actor模型与golang的CSP模型&#xff0c;经过一段时间的实战记录了本文。Actor vs CSP模型• 传统多线程的的共享内存&#xff08;ShareMemory&#xff09;模型使用l…

DateTimeToUnix/UnixToDateTime 对接时间转换

问题&#xff0c;通过毫秒数来解析出时间&#xff1a;&#xff08;很多对接的时候经常需要用到&#xff09; <?php $MyJson {"jingdong_vas_subscribe_get_responce":{"code":"0","item_code":"FW_GOODS-2236-1","…

【学生选课系统经典】VB与SQLSERVER连接:Windows应用工程案例

实验任务描述 1 用VB6访问SQLSERVER数据库(两种安全模式); 2 用VB6完成数据库指定表上的数据显示; 3 用VB6完成数据库指定表上的数据插入、删除和更新; 4 用VB6完成SQLSERVER2008数据库用户验证。 一、数据库系统 该实验中,所要求的数据库名称为SCHOOL,总共涉及以下表:

《假如编程是魔法之零基础看得懂的Python入门教程 》——(二)魔法实习生第一步了解魔杖的使用

学习目标 了解什么是开发环境了解python语言的环境安装了解python语言编程的编辑器工具 目录 第一篇&#xff1a;《假如编程是魔法之零基础看得懂的Python入门教程 》——&#xff08;一&#xff09;既然你选择了这系列教程那么我就要让你听得懂 第三篇&#xff1a;《假如编…

mysql5.7 only_full_group_by_Mysql5.7及以上版本 ONLY_FULL_GROUP_BY报错的解决方法

近期在开发过程中&#xff0c;因为项目开发环境连接的mysql数据库是阿里云的数据库&#xff0c;而阿里云的数据库版本是5.6的。而测试环境的mysql是自己安装的5.7。因此在开发过程中有小伙伴不注意写了有关group by的sql语句。在开发环境中运行是正常的&#xff0c;而到了测试环…

一款高速的NET版的离线免费OCR

PaddleOCR.Onnx一款基于Paddle的OCR&#xff0c;项目使用ONNX模型&#xff0c;速度更快。本项目同时支持X64和X86的CPU上使用。本项目是一个基于PaddleOCR的C代码修改并封装的.NET的工具类库。包含文本识别、文本检测、基于文本检测结果的统计分析的表格识别功能&#xff0c;同…

spring 注解简单使用

一、通用注解 1、项目结构&#xff1a; 2、新建Person类&#xff0c;注解Component未指明id&#xff0c;则后期使用spring获取实例对象时使用默认id"person"方式获取或使用类方式获取 package hjp.spring.annotation.commen;import org.springframework.stereotype.C…

《假如编程是魔法之零基础看得懂的Python入门教程 》——(三)使用初始魔法跟编程魔法世界打个招呼吧

学习目标 完成显示魔法的使用——输出print完成传入魔法的使用——输入input使魔法生效——运行python文件 目录 第一篇&#xff1a;《假如编程是魔法之零基础看得懂的Python入门教程 》——&#xff08;一&#xff09;既然你选择了这系列教程那么我就要让你听得懂 第二篇&am…

查缺补漏系统学习 EF Core 6 (一)

推荐关注「码侠江湖」加星标&#xff0c;时刻不忘江湖事掌握 ORM 开发方式是每一个 .NET 开发者所必备的技能&#xff0c;而且 .NET 平台有很多优秀的 ORM 框架。很多人都会诟病 .NET 官方标配的 Entity Framework&#xff0c;感觉其笨重难用、性能低下。但其实经过多年发展&am…

【经典回放】JavaScript学习详细干货笔记之(一)

【经典回放】JavaScript学习详细干货笔记之&#xff08;一&#xff09; 【经典回放】JavaScript学习详细干货笔记之&#xff08;二&#xff09; 【经典回放】JavaScript学习详细干货笔记之&#xff08;三&#xff09; 目录 一、为什么要学JavaScript 二、JavaScript经典案例 …