【5min+】传说中的孪生兄弟? Memory and Span

系列介绍

【五分钟的dotnet】是一个利用您的碎片化时间来学习和丰富.net知识的博文系列。它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net知识等等。
5min+不是超过5分钟的意思,"+"是知识的增加。so,它是让您花费5分钟以下的时间来提升您的知识储备量。

正文

在上一篇文章:《闪电光速拳? .NetCore 中的Span》 中我们提到了在.net core 2.x 所新增的一个类型:Span

它与咱们传统使用的基础类型相比具有超高的性能,原因是它减少了大量的内存分配和数据量复制,并且它所分配的数据内存是连续的。

但是您会发现它无法用在我们项目的某些地方,它独特的 ref结构 使它没有办法跨线程使用、更没有办法使用Lambda表达式。

特别是在AspNetCore中,咱们会使用到大量的异步操作方法。“所以,这个时候如果我们又想跨线程操作数据又想获得类似Span这样的性能怎么办呢?” 上一篇文章我们留下了这样的一个问题,所以现在就是到了还愿的时候了。它就是与Span一起发布的孪生兄弟: Memory

狮子座和射手座黄金圣斗士同样具备超越光速的能力

什么是Memory

那什么是Memory呢?不妨我们先来猜测一下,它的结构是什么样子。毕竟它是Span的孪生兄弟,而Span的结构我们在前面就了解过了:

public readonly ref struct Span<T>
{
    public void Clear();
    public void CopyTo([NullableAttribute(new[] { 0, 1 })] Span<T> destination);
    public void Fill(T value);
    public Enumerator GetEnumerator();
    public Span<T> Slice(int start, int length);
    public T[] ToArray();
    public override string ToString();

    //.....
}

当时我们说Span有各种缺陷的原因是由于它独特的 ref struct 关键字所导致的,导致它无法拆箱装箱、无法书写Lambda、无法跨线程等。但是它兄弟却可以克服缺点,所以我们想想它会和Span在声明上有哪些差距呢? 是的,您可能已经想到了:它不会有 ref 关键字了。

所以,我们看到它的内部结构就是酱紫的:

public readonly struct Memory<T>
{
    public static Memory<T> Empty { get; }
    public bool IsEmpty { get; }
    public int Length { get; }
    public Span<T> Span { get; }
    public void CopyTo([NullableAttribute(new[] { 0, 1 })] Memory<T> destination);
    public MemoryHandle Pin();
    public Memory<T> Slice(int start, int length);
    public T[] ToArray();
    public override string ToString();
}

和我们猜想的一样。它少了ref关键字,内部方法也和Span差不多(同样拥有CopyTo,Slice等),但是还是有一些差异,比如多了Pin方法,Span属性等。

被声明为ref struct的结构,叫做“ByRefLike”。所以在我们在进行反射的时候,我们使用Type会看到有这样一个属性:IsByRefLike

好像有点超纲了哈(>人<;)

按照MSDN给出的解释:

该结构是使用中的C# ref struct 关键字声明的。 不能将类似 byref 的结构的实例放置在托管堆上。

所以这也是为什么上一篇文章说的:Span只能放置在内存栈中的原因。

那么反过来想,没有了ref关键字之后。Memory是不是就可以放置在托管堆上了呢?是不是就可以进行拆装箱,克隆副本供其它线程的内存栈使用了呢? 好吧,可能是这样。所以这也许就是它能够被允许跨线程使用的原因吧。

进行到了这一步,那我们再回过头来想想Memory是什么呢? 其实现在我们心里其实都已经有个底了:

与 Span<T>一样,Memory<T> 表示内存的连续区域。 但 Span<T>不同,Memory<T> 不是ref 结构。 这意味着 Memory<T> 可以放置在托管堆上,而 Span<T> 不能。 因此,Memory<T> 结构与 Span<T> 实例没有相同的限制。 具体而言:

  • 它可用作类中的字段。
  • 它可跨 await 和 yield 边界使用。

除了 Memory<T>之外,还可以使用 System.ReadOnlyMemory<T> 来表示不可变或只读内存。

这是MSDN给出来的解释,不是我乱编的哈??!(虽然和我们上面猜的一模一样(●ˇ?ˇ●)

接下来,我们来看看他们到底有多像:

好吧,为了做该图我已经使用了美工必杀器 - ps??

有没有发现,除了名字之外,好像其它的都一模一样??。甚至直接连注释都懒得改了。

一样却又不一样

既然作为孪生兄弟,必然有一些共通之处。而Memory作为对Span的增强(应该也算不算增强吧),那么内部的实现可能很多会与Span相似。

是的,查看Memory的源代码您就会发现,它的内部某些方法就是通过Span来实现的:

public readonly struct Memory<T>
{
    public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
    public T[] ToArray() => Span.ToArray();
}

有关Memory的源代码,您可以点此查看:the source code of Memory

所以您会发现Memory是可以直接转换为Span的。但是Memory作为一个可以跨线程的类型被转换为Span是相对危险的,所以Dotnet Core的开发人员直接在备注上写了这样的文字:

Such a cast can only be done with unsafe or marshaling code,in which case that‘s the dangerous operation performed by the dev, and we‘re just following suit here to make it work as best as possible.

意思就是这种转换很危险,我来帮你做了算了。

如何使用

来吧,修改上面的Span会在Task种报错的例子:

public async Task MemoryCanInLambda(Memory<string> buffer)
{
    await Task.Factory.StartNew(() =>
    {
        buffer.Trim("s");
    });
}

此时我们就可以在异步中使用Memory了,采用连续内存+指针级别的操作方案来操作数据内容,岂不爽歪歪?

异步的数据交由Memory,同步的数据交由Span,ForExample:


static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream)
{
  int bytesRead = await stream.ReadAsync(buffer);
  return Checksum(buffer.Span.Slice(0, bytesRead));
  // Or buffer.Slice(0, bytesRead).Span
}
static int Checksum(Span<byte> buffer) { ... }

正是由于SpanMemory带来的巨大性能优化,所以.NET Core的开发者们做了一件非常疯狂的事:为.NET的库添加了数百个重载方法。 比如,您现在可以看到我们经常使用的Int.Parse方法居然支持了Span,它的签名是酱紫:

public static Int32 Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.Integer, [NullableAttribute(2)] IFormatProvider? provider = null);

除此之外,还有longdouble…………甚至连Guid和DateTime都有这样的重载。
还有其它常用的各种类也开始支持以Span作为参数的重载方法了,比如Random、StringBuilder等。

public StringBuilder Append(ReadOnlySpan<char> value);

先不谈重建这些基础常用类型的重载工作量有多大,我们应该想想.NET为什么要这么做呢?就是为了我们能够使用SpanMemory来代替我们现有的一些操作,从而提升性能。

那么仅仅是开发底层框架才适合用它们吗? 当然不是,就好比是截取字符串的操作,无论是底层框架还是应用程序级别的代码都会用到。所以如果有可能,而当我们的项目又正好是.netCore 2.x以上的版本,为何不去尝试使用下呢?

不要因为“我知道Span不过就是把原有的某某操作放到内存某处,不过如此”,就对它产生偏见。确实,Span的实现和简单,您如果有兴趣可以查看它的实现代码。.net core正在为它的实现和使用做巨大的适配工作,C# 从7.x 开始就不断对异步操作和内存分配进行优化,这或许也为我们未来.NET的发展给了一点点提示。加油,伟大的开发人员们。(? ?_?)?

最后,小声说一句:创作不易,点个推荐吧??

原文地址:https://www.cnblogs.com/uoyo/p/12218998.html

时间: 2024-08-04 10:11:27

【5min+】传说中的孪生兄弟? Memory and Span的相关文章

X86的孪生兄弟,Y86指令体系结构

X86的孪生兄弟,Y86指令体系结构 前言 为啥我的排版还是这么丑?排版如人啊,唉.说实话,上一章的内容我没搞明白,先不管了,先放一下,个人比较喜欢鲸吞的学习方式. 正文 Y86指令体系结构 Y86是一个指令体系结构(ISA),它是一个写这本书的作者出的指令集.目的是为了让我们更加清晰地了解ISA,就像在读编译原理的时候,作者会教你做个编译器是一样的道理. 我们学这个并不是为了设计指令集,因为这种工作几乎不可能发生在我们身上还是和编译原理一样,你的工作也不太可能写一个编译器,常遭一种语言,最多就

Android图表库MPAndroidChart(十)——散点图的孪生兄弟气泡图

Android图表库MPAndroidChart(十)--散点图的孪生兄弟气泡图 起泡图和散点图如出一辙,但是个人认为要比散点图好看一点,我们来看下实际的演示效果 这个和散点图的实现很相似,我们一起来看下 一.基本实现 先看下我的xml <com.github.mikephil.charting.charts.BubbleChart android:id="@+id/mBubbleChart" android:layout_width="match_parent&quo

策略模式的孪生兄弟——对状态模式的深度复习总结

俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的总结知识点如下: 和策略模式的比较 状态模式概念和例子 应用场景 责任链模式和状态模式对比 一种代码优化的思路 java.util.Iterator里也有状态模式的影子 状态模式的优缺点 有限状态机及其应用 前面有总结——策略模式,之前早就觉得策略和状态设计模式有一些相似…… 接口的常用用法都有什么?策略设计模式复习总结 我知道策略模式是对象的行为模式,其实就是对一系列级别平等的算法的封装,它不关心算法实现,让客户端去动态的

孪生兄弟状态模式与策略模式有什么区别,究竟该如何选择

都说状态模式和策略模式很像,它们的 UML 类图一样.这也说明,单纯从代码角度来讲,它们的本质一样,其实都是多态的应用.但它们实际所代表的的事物特征是有本质区别的,选择哪个设计模式,代表了你看待业务场景的角度.从合理角度地对业务进程抽象,选择恰当的设计模式,才能让代码有更好的结构. 这篇文章重点说说我对状态模式和策略模式区别的理解,以及如何选择. 一.策略模式 关于策略模式,我之前写过一篇笔记,不过是 C# 写的.策略模式解决了代码逻辑分支较多,对不同的分支,采取不同措施的问题.不熟悉策略模式的

上周热点回顾(1.20-1.26)

热点随笔: · 150行代码打造.net core生产力工具,你值得拥有 (billsking)· 裁员寒潮下,如何过冬 (渡码)· [新书推荐]<ASP.NET Core微服务实战:在云环境中开发.测试和部署跨平台服务> 带你走近微服务开发 (张善友)· [年终总结]马三京沪漂流记之2019年总结 (马三小伙儿)· 吸取教训:一段网上找的代码突然爆了,项目出现大BUG (陈宏鸿)· 2019国内某知名科技公司技术资料 (Jesai)· 为什么 K8s 在阿里能成功?| 问底中国 IT 技术演

.net下的span和memory

.net core 2.1的重头戏就是性能,其中最重要的两个类就是span和memory,本文这里简单的介绍一下这两个类的使用. 什么是 Span<T> Span<T> 是新一种新值类型.它表示一段连续的区域,它通常和数组关联,表示数组中的一部分内存. var????????arr???=?new?byte[10];Span<byte>?bytes?=?arr; 也可以取数组中的一部分: var?bytes?=?new?Span<byte>(arr,?3,?

Span&lt;T&gt;

Introduction Span<T> is a new type we are adding to the platform to represent contiguous regions of arbitrary memory, with performance characteristics on par with T[]. Its APIs are similar to the array, but unlike arrays, it can point to either mana

编译调试 .NET Core 5.0 Preview 并分析 Span 的实现原理

很久没有写过 .NET Core 相关的文章了,目前关店在家休息所以有些时间写一篇新的??.这次的文章主要介绍如何在 Linux 上编译调试最新的 .NET Core 5.0 Preview 与简单分析 Span 的实现原理.微软从 .NET Core 5.0 开始把 GIT 仓库 coreclr 与 corefx 合并移动到了 runtime 仓库,原有仓库仅用于维护 .NET Core 3.x,你可以从以下地址查看最新的源代码: https://github.com/dotnet/runti

重拾算法之路——递归与分治基础

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 这个周末家里有点事,回了趟家,就断了一些学习计划.. 抓紧补上! 第一个算法--递归与分治 都知道,分治算法基本思想是 将一个难以直接解决的问题,分割成一些规模小的相同问题,以便各个击破,分而治之, 这样,我们就可以将一个复杂的算法,类型不变,规模越来越小,最终