关键要点
1).NET Core是跨平台的,可运行在Windows、Linux、Mac OS X和更多平台上;与.NET相比,发布周期要短得多。大多数.NET Core都是通过NuGet软件包交付的,可以很容易地发布和升级。

2)更快速的发布周期对性能提升工作以及改进诸如SortedSet和LINQ . tolist()方法等语言结构性能的大量工作都有着特别的帮助。

3)通过引入了System.ValueTuple和Span这样的类型,更快的周期和更容易的升级也为迭代改进 .NET Core性能的新想法带来了机会。

4)这些改进之后可以反馈到完整的 .NET 框架中。

  随着.NET Core2.0的发布,微软有了下一个主要版本的通用目标,而模块化、跨平台和开源平台最初发布于2016年。.NET Core 已经创建了许多api,这些api可以在.NET 框架的当前版本中使用。它最初是为下一代ASP.NET创建的解决方案,但现在是驱动、是许多其他场景的基础,包括物联网、云和下一代移动解决方案。在本系列中,我们将探讨一些.NET Core的好处,以及它如何不仅能让传统的.NET开发人员受益,还能让所有需要为市场带来健壮、高性能和经济解决方案的技术人员受益。

  现在,.NET Core正在路上,微软和开源社区可以在框架的新特性和增强上进行更快速的迭代。在.NET Core中,性能是持续关注的一个领域:.NET Core在执行速度和内存分配方面都带来了许多优化。

  在这篇文章中,我们将讨论一些优化,以及如何在以后的性能工作中更多地使用连续流或Span<T>,为我们的开发人员带来帮助。

  .NET 和.NET Core

  在深入研究之前,让我们先看看完整的.NET框架(为方便起见我们称之为.NET)和.NET Core之间的主要区别。为了简化问题,让我们假设两个框架都遵循.NET标准,它本质上是一个规范,定义了所有.NET的基类库基线。这使得两个世界非常相似,除了两个主要的区别:

  首先,.NET主要是在Windows上的,而.NET Core是跨平台的,可运行在Windows、Linux、Mac OS X和更多平台上。

  第二,发布周期非常不同。.NET作为一个完整的框架安装程序,它是系统范围的,通常是Windows安装的一部分,使发布周期更长。对于.NET Core,在一个系统上可以有多个.NET Core安装,而且没有长时间的发布周期:大多数.NET Core是以NuGet包交付的,可以轻松地发布和升级。

  最大的优点是.NET Core世界可以更快地迭代并尝试新概念,并最终将它们反馈到完整的.NET框架中,作为未来.NET标准的一部分。

  经常(但不总是),.NET Core的新特性是由C#语言设计驱动的。因为框架可以更快地进化,语言也可以。一个快速发布周期和性能增强的主要例子是System.ValueTuple。C#和VB.NET 15引入了“值元组”,这很容易添加到.NET Core,因为更快的发布周期,针对完整的.NET 4.5.2和更早的版本,它可以作为一个NuGet包用于完整的.NET。在.NET 4.7中也可以仅成为完整的.NET框架的一部分。

  现在让我们来看看其中的一些性能和内存改进。

  备注:

  Nuget是ASP .NET Gallery 的一员。NuGet 是免费、开源的包管理开发工具,专注于在 .NET 应用开发过程中,简单地合并第三方的组件库。

  .NET Core的性能改进

  .NET Core工作的优点之一是,许多东西要么需要重新构建,要么需要从完整的.NET框架中移植。让所有的内部构件在flux中运行一段时间,再加上快速发布周期,提供了一个在代码中进行一些性能改进的机会。以前,这些性能改进几乎被认为是“不要碰,它刚刚正常工作!“。

  让我们从SortedSet和它的Min和Max的实现开始。SortedSet是通过利用自平衡树结构,以有序顺序维护的对象集合。在此之前,从该集合中获取最小或最大对象需要向下遍历树(或向上),调用每个元素的委托,并将返回值设置为当前元素的最小值或最大值,最终到达树的顶部或底部。调用该委托并传递对象意味着有相当多的开销。直到有一个开发人员看到了这棵树,并删除了不需要的委托调用,因为它没有提供任何值。他自己的基准测试显示有30%-50%的性能提升。

  另一个很好的例子是在LINQ中,在常用的. tolist()方法中更具体。大多数LINQ方法在IEnumerable上作为扩展方法操作,以提供查询、排序和诸如. tolist()之类的方法。通过这样做,我们不必关心底层IEnumerable的实现,只要能够遍历它就行了。

  缺点是,当调用. tolist()时,我们不知道要创建的列表的大小,只枚举enumerable中的所有对象,这把即将返回的列表的大小增加了一倍。这有点愚蠢,因为它潜在地浪费了内存(和CPU周期)。因此,如果底层IEnumerable实际上是具有已知大小的列表或数组,那么就会更改为创建一个已知大小的列表或数组。来自.NET团队的基准测试显示,这些数据的吞吐量增加了4倍。

  当查看GitHub上CoreFX实验室存储库中的pull请求时,我们可以看到微软和社区都做出了大量的性能改进。因为.NET Core是开源的,你也可以提供性能修正。其中大多数都是:对.NET中的现有类进行修复。但还有更多:.NET Core还介绍了一些关于性能和内存的新概念,这些概念不仅仅是修复这些现有的类。

  使用System.ValueTuple减少资源分配

  假设我们想从一个方法返回多个值。

  以前,我们要么使用out参数,这让人用起来非常不爽,而且在编写async方法时也不支持。

  另一种选择是使用System.Tuple作为返回类型,但它分配了一个对象,并且具有相当不友好的属性名称(Item1, Item2,…)。

  第三种选择是使用特定类型或匿名类型,但是在编写代码时这种做法会引入开销,因为我们需要定义类型,而且如果我们需要的是嵌入在该对象中的值,它也会造成不必要的内存分配。

  遇到元组返回类型,由System.ValueTuple支持。C# 7和VB.NET 15添加了一个语言特性,可以从一个方法返回多个值。下面是之前和之后的示例:

//之前:
private Tuple<string, int> GetNameAndAge()
{
return new Tuple<string, int>("Maarten", );
} //之后:
private (string, int) GetNameAndAge()
{
return ("Maarten", );
}

  在第一个例子中,我们分配一个元组。因为几乎不必做什么额外的工作,分配是在托管堆上完成的。在某个时刻,垃圾回收(GC)将不得不清理它。

  在第二种情况下,编译器生成的代码使用的是ValueTuple类型,它本身就是一个struct,并在堆栈上创建,这使我们能够访问我们想要处理的两个值,同时确保在包含的数据结构上不需要做垃圾回收。

  如果我们使用ReSharper的中间语言(IL)查看器查看编译器在上面示例中生成的代码,那么就会很明显看到这种差异。这里有两个方法签名:

//之前:.method
private hidebysig instance class [System.Runtime]System.Tuple`<string, int32> GetNameAndAge() cil managed
{
// ...
} //之后:.method
private hidebysig instance valuetype [System.Runtime]System.ValueTuple`<string, int32> GetNameAndAge() cil managed
{
// ...
}

  我们可以清楚地看到第一个示例返回一个类的实例,第二个例子返回一个值类型的实例。

  类是在托管堆中分配的(由CLR跟踪和管理,并受垃圾收集的管制,是可变的),而值类型分配在堆栈上(速度快且较少的开销,是不可变的)。简而言之:System.ValueTuple本身并没有被CLR跟踪,它只是作为我们关心的嵌入值的一个简单容器。

  请注意,在其优化的内存使用情况下,像元组解构这样的特性是非常令人愉快的副产品,它使这部分语言和框架都成为了这一部分。

  使用Span<T>减少子字符串的内存分配

  在前一节中,我们已经讨论了栈和托管堆。大多数.NET开发人员只使用托管堆,但.NET有三种类型的内存可供使用,这取决于具体情况:

  1)栈内存:我们通常分配的值类型的内存空间,比如int, double, bool,……它非常快(通常在CPU的缓存中使用),但大小有限(通常小于1 MB)。富有挑战精神的开发人员会使用stackalloc关键字添加自定义对象,但要知道它们是有危险性的,因为在任何时间都可能发生StackOverflowException,使我们的整个应用程序崩溃。

  2)非托管内存:没有垃圾收集器的内存空间,我们必须自己使用像Marshal.AllocHGlobal 和Marshal.FreeHGlobal之类的方法预订和释放内存。

  3)托管内存/托管堆:垃圾收集器释放已经不再使用的内存空间,使我们大多数人都过着无忧无虑的程序员生活,很少有内存问题。

  它们都有各自的优缺点,并有特定的用例。但是,如果我们想要编写一个与所有这些内存类型兼容的库该怎么办呢? 我们必须分别为他们提供方法。一个针对托管对象,另一个针对指针指向堆栈上或非托管堆上的对象。一个很好的例子就是创建一个字符串的子字符串。我们需要获取一个System.String并返回一个新System.String的方法,即要处理的托管版本的子字符串。非托管/堆栈版本将使用char*(是的,一个指针!)和字符串的长度,并返回类似的指向结果的指针。难以控制…

  这个类型在System.Memory.dll程序集中,通过NuGet包引入了一个新的Span<T>结构。它是一个值类型(因此没有被垃圾收集器跟踪),它试图统一对任何底层内存类型的访问。它提供了一些方法,但本质上是这样的:

  1)一个T的引用;

  2)一个可选的开始索引;

  3)一个可选的长度;  

  一些实用函数可以抓取一个Span<T>的切片,复制内容,…

  把它想成这个(伪代码):

public struct Span<T>
{
ref T _reference;
int _length;
public ref T this[int index] { get {...} }
}

  不管我们是使用字符串、char[]甚至是未管理的char*来创建一个Span<T>, Span<T>对象都提供了相同的函数,比如返回索引中的元素。可以把它看作是T[],其中T可以是任何类型的内存。如果我们想要编写一个子Substring()方法来处理所有类型的内存,那么我们所要关心的就是正在使用的Span<char>是如何工作的(或者它的不可变版本,ReadOnlySpan<T>):

  ReadOnlySpan<char> Substring(ReadOnlySpan<char> source, int startIndex, int length);

  这里的source参数可以是基于System.String的span,或者是未管理的char*,我们不需要关心。

  而是让我们暂时忘掉内存类型不可知的方面,并关注性能。如果我们要为System.String编写一个Substring()方法,我们可能会想到的:

  string Substring(string source, int startIndex, int length)

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);
}

  这很好,但实际上我们正在创建子字符串的副本。如果我们调用Substring(“Hello World!”,0,5),我们在内存中有两个字符串: “Hello World”和“Hello”可能会浪费内存空间,我们的代码仍然需要将数据从一个数组复制到另一个数组,以实现这一点,消耗了CPU周期。我们的实现并不坏,但也不理想。

  想象一下一个web框架的实现,它使用上面的代码从一个包含header和body的HTTP请求中获取请求体。我们必须分配具有重复数据的大块内存:一个具有整个传入请求的内存和一个仅包含请求体的子字符串。然后是需要从原始字符串复制数据到子字符串的开销。

  现在,让我们用ReadOnlySpan<T>来重写它:

static ReadOnlySpan<char> Substring(ReadOnlySpan<char> source, int startIndex, int length)
{
return source.Slice(startIndex, length);
}

  好吧,它变短了,但其实还有更多变化。由于实现了方法Span<T>,所以我们的方法不返回源数据的副本,而是返回引用源的子集的Span<T>。

  或者在将HTTP请求拆分为header和body的例子中:我们有3个Span:传入的HTTP请求,指向原始数据的头部分的一个span<T>,指向请求体的另一个Span<T>。数据在内存中只有一份(创建第一个Span的数据),其他所有的数据只会指向原始数据的切片。没有重复数据,没有复制和复制数据的开销。

  结论

  随着.NET Core和更快的发布周期,微软和.NET Core的开源社区可以在与性能相关的新特性上快速迭代。我们已经看到框架中很多改进现有代码和构造的工作,比如改进LINQ的. tolist()方法。

  更快的周期和更容易的升级也带来了迭代改进.NET Core性能的新想法的机会,通过引入诸如System.ValueTuple and Span<T>之类的类型,使. net开发人员更自然地使用我们在运行时可用的不同类型的内存,同时避免与它们相关的常见缺陷。

  想象一下,如果一些.net基类被重写为Span<T>实现,诸如字符串UTF解析、加密操作、web解析和其他典型的CPU和内存消耗任务。这将对框架带来很大的改进,并且所有的. net开发人员都将受益。事实证明,这正是微软计划要做的事情! .NET Core的性能前景光明!

参考链接:http://www.infoq.com/cn/articles/performance-net-core

性能是.NET Core的一个关键特性的更多相关文章

  1. innodb关键特性之double write

    # 脏页刷盘的风险 两次写的原理机制 1.解决问题 2.使用场景 3.doublewrite的工作流程 4.崩溃恢复 # doublewrite的副作用 1.监控doublewrite负载 2.关闭d ...

  2. Span<T>和ValueTuple<T>性能是.Net Core非常关键的特性

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

  3. InnoDB关键特性学习笔记

    插入缓存 Insert Buffer Insert Buffer是InnoDB存储引擎关键特性中最令人激动与兴奋的一个功能.不过这个名字可能会让人认为插入缓冲是缓冲池中的一个组成部分.其实不然,Inn ...

  4. SQL Server2014 SP2关键特性

    SQL Server2014 SP2关键特性 转载自:https://blogs.msdn.microsoft.com/sqlreleaseservices/sql-2014-service-pack ...

  5. SQL Server 2016 CTP2.3 的关键特性

    SQL Server 2016 CTP2.3 的关键特性 数据库方面的增强 Row Level Security已经支持In-memory OLTP 表.用户现在可以对内存优化表实施row-level ...

  6. SQL Server 2016 CTP2.2 的关键特性

    SQL Server 2016 CTP2.2 的关键特性 正如微软CEO 说的,SQL Server2016 是一个Breakthrough Flagship  Database(突破性的旗舰级数据库 ...

  7. Entity Framework Core 2.0 新特性

    本文翻译来自:https://docs.microsoft.com/en-us/ef/core/what-is-new/index 一.模型级查询过滤器(Model-level query filte ...

  8. InnoDB的关键特性-插入缓存,两次写,自适应hash索引

    InnoDB存储引擎的关键特性包括插入缓冲.两次写(double write).自适应哈希索引(adaptive hash index).这些特性为InnoDB存储引擎带来了更好的性能和更高的可靠性. ...

  9. ASP.NET MVC Core的TagHelper (高级特性)

    这篇博文ASP.NET MVC Core的TagHelper(基础篇)介绍了TagHelper的基本概念和创建自定义TagHelper的方式,接着继续介绍一些新的看起来比较高级的特性.(示例代码紧接着 ...

随机推荐

  1. C#后端接收form-data,创建实体类

    public class Para_list //实体类 { public long ParemeterID { get; set; } public string Name { get; set; ...

  2. “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  3. python 函数中使用全局变量

    python 函数中如果需要使用全局变量,需要使用 global + 变量名 进行声明, 如果不声明,那么就是重新定义一个局部变量,并不会改变全局变量的值 n [1]: a = 3 In [2]: d ...

  4. 在Load average 高的情况下如何鉴别系统瓶颈

    在Load average 高的情况下如何鉴别系统瓶颈.是CPU不足,还是io不够快造成? 或是内存不足? 一:查看系统负载vmstat procs -----------memory-------- ...

  5. 为autoLayout 增加标识符,方便调试

     如上图,是一个十分简单的布局. root view 上加了一个 button 和一个 webview. 不加标识符的样子 视图层级中没有标识  只有 UIView.WKWebView 之类,如果 ...

  6. ngRoute 与ui.router区别

    angular路由 路由 (route) ,几乎所有的 MVC(VM) 框架都应该具有的特性,因为它是前端构建单页面应用 (SPA) 必不可少的组成部分. 那么,对于 angular 而言,它自然也有 ...

  7. python 爬虫之 正则的一些小例子

    什么是正则表达式 正则表达式是对字符串操作的一种逻辑公式,就是 事先定义好的一些特定字符.及这些特定字符的组合,组成一个“规则字符”,这个“规则字符” 来表达对字符的一种过滤逻辑. 正则并不是pyth ...

  8. iOS 设置textfield的最大文本长度

    //在现实开发中  需要控制文本输入长度 并实时做短信验证,代码如下 [self.textField addTarget:self action:@selector(codeChange:) forC ...

  9. ubuntu sudo: pip:找不到命令

    编辑文件 /etc/sudoers sudo vi /etc/sudoers 将Defaults env_reset ,改为 Defaults    !env_reset 编辑文件-/.bashers ...

  10. Delphi获取IdHTTP1.Get(url)的返回参数

    var   ss: TStringStream; begin   ss := TStringStream.Create('');   idHTTP1.get(url, ss);   ss.Positi ...