先谈一下我对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. 视图:操作结果会直接体现到底层的连续内存。

至此我们来看一个简单的用法, 利用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;}}}
}

压测解读:

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

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

2. ref struct[3]

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

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

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

引用链接

[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<T>数据结构相关推荐

  1. Span 有多强大?玩转各种文字特效

    作者:徐宜生 本文转自:Android 群英传 Span的基础性概念分析 国际惯例,官网镇楼,这是对Span最好的全局概览. https://developer.android.com/guide/t ...

  2. 真·富文本编辑器的演进之路-Span开胃菜

    Span的基础性概念分析 国际惯例,官网镇楼,这是对Span最好的全局概览. https://developer.android.com/guide/topics/text/spans Span种类 ...

  3. TCMalloc(Thread-Caching malloc) 基本设计原理

    文章目录 背景 如何使用 架构概览 1. TCMalloc Front-end 1.1 小对象和大对象的内存分配过程 1.2 内存释放过程 1.3 Per-CPU mode 1.4 Per-threa ...

  4. 前端对div连线_《前端图形学从入门到放弃》003 三维世界

    从本篇起,我们将正式进入webgl的3D世界 本篇涵盖的内容包括: webgl它在干啥? 如何画一个正方体? 如何成为一个"有深度"的正方体? 正方体要离家出走了! webgl它在 ...

  5. 伙伴系统之避免碎片--Linux内存管理(十六)

    原文链接:https://blog.csdn.net/gatieme/article/details/52694362 日期 内核版本 架构 作者 GitHub CSDN 2016-09-28 Lin ...

  6. tcmalloc源码分析

    导语 目前工作中主要使用golang开发,想学习一下golang的内存分配实现原理,在阅读内存分配相关代码的时候,发现会涉及垃圾回收.协程调度.系统调用.plan6汇编代码,增加了学习的难度,因为go ...

  7. 真·富文本编辑器的演进之路

    Span的基础性概念分析 国际惯例,官网镇楼,这是对Span最好的全局概览. https://developer.android.com/guide/topics/text/spans Span种类 ...

  8. golang内存管理简介

    文章目录 基础概念 span cache central heap 分配过程 go runtime抛弃了传统的内存分配方式,改为自主管理.其内存分配算法主要源自 Google为C语言开发的 TCMal ...

  9. MTK Android添加驱动模块

    1 [编写linux驱动程序] 1.1 一.编写驱动核心程序 1.2 二.配置Kconfig 1.3 三.配置Makefile 1.4 四.配置系统的autoconfig 1.5 五.编译 2 [编写 ...

最新文章

  1. SSE3和SSSE3 Intrinsics各函数介绍
  2. [转载 js] YUI解决mouseout事件冒泡的办法
  3. 在细分场景的时代,如何反欺诈和防止内外勾结?
  4. 重温Android四大组件(一)—Activity的生命周期
  5. Extjs入门(07) 滚动条autoScroll:true,
  6. java没有释放内存_java – G1年轻的GC没有释放内存 – 空间耗尽
  7. 在Teams中对网站的URL特殊解析
  8. cad转dxf格式文件太大_如何玩转CAD看图?1分钟,一款完全免费的高效软件,解决所有看图...
  9. C#语言-NPOI.dll导入Excel功能的实现
  10. 【每日算法Day 94】经典面试题:机器人的运动范围
  11. php将中文编译成字符串,PHP将汉字字符串转换为数组
  12. Cisco ASA防火墙基础--转载http://wenzhongxiang.blog.51cto.com/6370734/1249746
  13. Hash表的平均查找长度ASL计算方法
  14. jquery自定义插件_创建一个自定义jQuery插件
  15. 伟大的民族英雄赵充国
  16. 太励志!考研哈工大高数39分,但逆袭成为院士,做出诺奖级发现
  17. 学会远程开机之后,发现远程控制软件特别多,哪些好用?哪些免费?
  18. 【博客559】更出色的网络监控采集方案---Telemetry(遥测技术)
  19. 奇门遁甲排盘方:定局
  20. 注意论文投稿风险,现投期刊会不会成为预警期刊呢?

热门文章

  1. 新媒体管理师是什么?为什么那么多人都在考你知道吗?
  2. Linux中级实战专题篇一:nginx服务(特性优势,yum安装,编译安装详解,虚拟主机技术详解)
  3. Python爬虫1:批量获取电影标题和剧照
  4. Neo4j:BBC冠军联赛图表
  5. Axios-Poly马尔文帕纳科荧光光谱仪电源维修PW4400高压发生器维修
  6. 取消英文google的Instant predictions
  7. 嵌入式Linux系统基础程序开发
  8. [附源码]计算机毕业设计JAVA壹家吃货店网站
  9. Scrapy爬取重庆安居客二手房并存入mysql数据库(上)
  10. StringBuilder与String类型