性能是.Net Core一个非常关键的特性,今天我们重点研究一下ValueTuple<T>和Span<T>.

一、方法的多个返回值的实现,看ValueTuple<T>

日常开发中,假如我们一个方法有多个返回值,我们可能会用Out出参,或者使用一个自定义类/匿名类型,或者Tuple<T>.

  • Out出参可以使用,但是在编写Async方法时不支持。
  • 自定义类/匿名类型,需要我们根据返回值的结构,自定义一个类型,带来性能开销,同时增加了编码工作量,同时需要考虑跨域序列化的问题。
  • .Net Framework 4.0后引入了Tuple<T>元组,但是Item1,Item2,...不够友好,方法调用方需要了解分别代表的含义。

现在我们看看ValueTuple<T>的实现

C# 7支持返回多个值的语言特性,我们写两个示例代码Tuple<T>和ValueTuple<T>,对比一下:

         /// <summary>
/// Tuple
/// </summary>
/// <returns></returns>
private Tuple<string, List<int>> GetValues()
{
return new Tuple<string, List<int>>("C7", new List<int> { , , });
} /// <summary>
/// ValueTuple
/// </summary>
/// <returns></returns>
private (string, List<int>) GetValuesN()
{
return ("C7", new List<int> { , , });
}

Tuple的示例中,代码声明了一个Tuple元组,内存在托管堆上统一管理,GC垃圾回收在指定时机下回收。

ValueTuple示例中,编译器生成的代码使用的是ValueTuple,其本身就是一个struct,在栈上创建,这使我们既可以访问这个返回值数据,同时确保在包含的数据结构上不需要做垃圾回收。

我们通过IL Spy看下编译后的代码:

上图可以看到:

第一个方法GetValues,返回class [System.Runtime]System.Tuple`2<string, class [System.Collections]System.Collections.Generic.List`1<int32>>,一个类的实例

第二个方法GetValuesN,返回valuetype [System.Runtime]System.ValueTuple`2<string, class [System.Collections]System.Collections.Generic.List`1<int32>>,一个值类型的实例。

类是在托管堆中分配的 (由 CLR 跟踪和管理,并受垃圾收集的管制,是可变的),而值类型分配在堆栈上 (速度快且较少的开销,是不可变的)。

System.ValueTuple 本身并没有被 CLR 跟踪,它只是作为我们使用的嵌入值的一个简单容器。

ValueTuple<T>作为C#7.0支持方法多返回值,的确在底层实现上考虑了性能表现(内存),同时编码上给我们带来了更愉快的语法特性!

二、从字符串操作看Span<T>

大多数.Net开发场景,只使用到了托管堆(由CLR统一管理),其实.Net 有三种类型的内存可以使用,不过要看具体的使用场景。

  • 栈内存:我们通常分配的值类型的内存空间,比如 int, double, bool,……它非常快 (通常在 CPU 的缓存中使用),但大小有限 (通常小于 1 MB)。当然,有些开发人员会使用 stackalloc 关键字添加自定义对象,但要知道它们是有危险性的,因为在任何时间都可能发生 StackOverflowException,使我们的整个应用程序崩溃。
  • 非托管内存:没有垃圾收集器的内存空间,必须自己使用像 Marshal.AllocHGlobal 和 Marshal.FreeHGlobal 之类的方法预订和释放内存。
  • 托管内存 / 托管堆:通过GC垃圾收集器释放已经不再使用的内存空间,使我们大多数人都过着无忧无虑的程序员生活,很少有内存问题。

上述三种类型的内存,都有各自的优缺点,特点的使用场景。如果我们设计一个兼容支持上述三种类型的Lib,需要分别提供两种实现,一种是支持托管堆的,一种是支持栈和非托管内存的。比如说SubString。

我们先看第一种支持托管推的SubString实现:

 string Substring(string source, int startIndex, int length)
{
var result = new char[length];
for (var i = ; i < length; i++)
{
result[i] = source[startIndex + i];
} return new string(result);
}

上述方法内部声明了新的string对象和字符数组,这无疑带来了内存和CPU消息,实现的并不差,但是也不理想。

继续看第二种支持栈和非托管内存的,使用 char*(是的,一个指针!) 和字符串的长度,并返回类似的指向结果的指针。实现上就有点小复杂了。

此时,我们看.Net Core新引入的System.Memory命名空间下的Span<T>. 首先它是一个值类型 (因此没有被垃圾收集器跟踪),它试图统一对任何底层内存类型的访问。看一下它的内部结构:

  // Constructor for internal use only.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Span(ref T ptr, int length)
{
Debug.Assert(length >= ); _pointer = new ByReference<T>(ref ptr);
_length = length;
} public ref T this[Index index]
{
get
{
// Evaluate the actual index first because it helps performance
int actualIndex = index.GetOffset(_length);
return ref this [actualIndex];
}
}

不管我们是使用字符串、char[] 甚至是未管理的 char* 来创建一个 Span<T>, Span<T> 对象都提供了相同的函数,比如返回索引中的元素。可以把它看作是 T[],其中 T 可以是任何类型的内存。

我们用Span<T>编写一个 Substring() 方法

Span<char> SubString(Span<char> source, int startIndex, int length)
{
return source.Slice(startIndex, length);
}

上述方法不返回源数据的副本,而是返回引用源的子集的 Span<T>,对比第一种SubString实现:没有重复数据,没有复制和复制数据的开销。

总结一下:

.NetCore中通过引入诸如 System.ValueTuple and Span<T> 之类的类型,使. net 开发人员更自然地使用在运行时可用的不同类型的内存,同时避免与它们相关的常见缺陷。这种统一带来了性能提升的同时,也简化了我们日常的编码。

周国庆

2019/3/24

.Net Core技术研究-Span<T>和ValueTuple<T>的更多相关文章

  1. .NET Core技术研究系列-索引篇

    随着.NET Core相关技术研究的深入,现在将这一系列的文章,整理到一个索引页中,方便大家翻阅查找,同时,后续也会不断补充进来. .NET Core技术研究-WebApi迁移ASP.NET Core ...

  2. .NET Core技术研究-主机

    前一段时间,和大家分享了 ASP.NET Core技术研究-探秘Host主机启动过程 但是没有深入说明主机的设计.今天整理了一下主机的一些知识,结合先前的博文,完整地介绍一下.NET Core的主机的 ...

  3. ASP.NET Core技术研究-全面认识Web服务器Kestrel

    因为IIS不支持跨平台的原因,我们在升级到ASP.NET Core后,会接触到一个新的Web服务器Kestrel.相信大家刚接触这个Kestrel时,会有各种各样的疑问. 今天我们全面认识一下ASP. ...

  4. ASP.NET Core技术研究-探秘Host主机启动过程

    当我们将原有ASP.NET 应用程序升级迁移到ASP.NET Core之后,我们发现代码工程中多了两个类Program类和Startup类. 接下来我们详细探秘一下通用主机Host的启动过程. 一.P ...

  5. .Net Core技术研究-WebApi迁移ASP.NET Core2.0

    随着ASP.NET Core 2.0发布之后,原先运行在Windows IIS中的ASP.NET WebApi站点,就可以跨平台运行在Linux中.我们有必要先说一下ASP.NET Core. ASP ...

  6. .NET Core技术研究-配置读取

    升级ASP.NET Core后,配置的读取是第一个要明确的技术.原先的App.Config.Web.Config.自定义Config在ASP.NET Core中如何正常使用.有必要好好总结整理一下,相 ...

  7. .NET Core技术研究-中间件的由来和使用

    我们将原有ASP.NET应用升级到ASP.NET Core的过程中,会遇到一个新的概念:中间件. 中间件是ASP.NET Core全新引入的概念.中间件是一种装配到应用管道中以处理请求和响应的软件.  ...

  8. ASP.NET Core技术研究-探秘依赖注入框架

    ASP.NET Core在底层内置了一个依赖注入框架,通过依赖注入的方式注册服务.提供服务.依赖注入不仅服务于ASP.NET Core自身,同时也是应用程序的服务提供者. 毫不夸张的说,ASP.NET ...

  9. .NET Core技术研究-通过Roslyn代码分析技术规范提升代码质量

    随着团队越来越多,越来越大,需求更迭越来越快,每天提交的代码变更由原先的2位数,暴涨到3位数,每天几百次代码Check In,补丁提交,大量的代码审查消耗了大量的资源投入. 如何确保提交代码的质量和提 ...

随机推荐

  1. sybase central 报 NullPointerException 解决

    准备发布版本,但是要创建数据库的时候遇到了问题,发现之前可以正常打开的sybase Central 现在无法打开了.苦恼一段时间后找到如下解决方法. 报错如下: 解决如下: 正常打开:

  2. Chapter 2 Basic Elements of JAVA

    elaborate:详细说明 Data TypesJava categorizes data into different types, and only certain operationscan ...

  3. STS的安装以及IDEA安装和破解过程

    一.STS的下载 1·下载地址:直接百度搜索STS,选择Download STS 3 2.选择电脑对应的版本 直接下载 3.安装包解压后在有jdk的情况下就可以直接使用 二.IDEA的安装 下载网址: ...

  4. Java当中的IO一

    1.IO操作的目标 什么是IO操作的目标? 输入: 从数据源当中读取数据 输出: 将数据写入到数据目的地当中 有数据进入到程序当中,这个过程就可以被叫做输入 流:即在数据源与程序之间建立的传输通道 2 ...

  5. AWS Nginx Started but not Serving AWS上Nginx服务器无法正常工作

    After install the Nginx on AWS instance, and visit your public ip address, you might see the followi ...

  6. Wireshark简单使用教程2——附视频

    视频链接https://www.bilibili.com/video/av35336089/ 目录 对抓取的流量包进行简单的说明 Wireshark的捕获过滤器和显示过滤器 内容 1.对抓取的流量包进 ...

  7. [Codeforces Round #438][Codeforces 868C. Qualification Rounds]

    题目链接:868C - Qualification Rounds 题目大意:有\(n\)个题目,\(k\)个人,每个人可能做过这\(n\)个题里的若干道,出题方要在这\(n\)个题目里选若干个出来作为 ...

  8. radio选中与不选中

    $("input[name='bankCardType']").val("sdf"); $('input:radio[name="bankCardTy ...

  9. java_基础_类加载

    类加载:Java命令的作用是启动虚拟机,虚拟机通过输入流,从磁盘上将字节码文件(.class文件)中的内容读入虚拟机,并保存起来的过程就是类加载. 类加载特性 :      *在虚拟机的生命周期中一个 ...

  10. day24:继承

    1,复习1 # 面向对象编程 # 思想:角色的抽象,创建类,创建角色(实例化),操作这些示例 # 面向对象的关键字 class 类名: 静态属性 = 'aaa' def __init__(self): ...