快捷搜索:  汽车  科技

net和core哪个好?是什么让.NetCore

net和core哪个好?是什么让.NetCoreusing System;using System.Diagnostics;using System.Collections.Generic;using System.Linq;public class Test在个人电脑的.NET Framework上,这段代码需要大约7.7秒执行完成。在.NET Core 2.0上,减少到大约0.013s(改进改变了算法的复杂性,集合越大,节省的时间越多)。在其他情况下,通过更改操作算法的复杂性,可以更快地进行操作。编写软件时,最初编写的一个简单实现,虽然是正确的,但是这样实现往往不能表现出最佳的性能,直到特定的场景出现时,才考虑如何提高性能。例如,SortedSet <T>的ctor最初以相对简单的方式编写,由于使用O(N ^ 2)算法来处理重复项,因此不能很好地处理复杂性。该算法在PRnetnet / corefx#1955中的.NET

net和core哪个好?是什么让.NetCore(1)

.NET Core(开放源代码,跨平台,x-copy可部署等)有许多令人兴奋的方面,其中最值得称赞的就是其性能了。

感谢所有社区开发人员对.NET Core做出的贡献,其中的许多改进也将在接下来的几个版本中引入.NET Framework。

本文主要介绍.NET Core中的一些性能改进,特别是.NET Core 2.0中的,重点介绍各个核心库的一些示例。

集合

集合是任何应用程序的基石,同时.NET库中也有大量集合。.NET库中的一些改进是为了消除开销,例如简化操作以便更好的实现内联,减少指令数量等。例如,下面的这个使用Q<T>的例子:

using System;using System.Diagnostics;using System.CollectIOns.Generic;public class Test

PR dotnet/corefx #2515移除了这些操作中相对复杂的模数运算,在个人计算机,以上代码在.NET 4.7上产生如下输出:

00:00:00.9392595 00:00:00.9390453 00:00:00.9455784 00:00:00.9508294 00:00:01.0107745

而使用.NET Core 2.0则会产生如下输出:

00:00:00.5514887 00:00:00.5662477 00:00:00.5627481 00:00:00.5685286 00:00:00.5262378

由于这是挂钟时间所节省的,较小的值计算的更快,这也表明吞吐量增加了约2倍!

在其他情况下,通过更改操作算法的复杂性,可以更快地进行操作。编写软件时,最初编写的一个简单实现,虽然是正确的,但是这样实现往往不能表现出最佳的性能,直到特定的场景出现时,才考虑如何提高性能。例如,SortedSet <T>的ctor最初以相对简单的方式编写,由于使用O(N ^ 2)算法来处理重复项,因此不能很好地处理复杂性。该算法在PRnetnet / corefx#1955中的.NET Core中得到修复。以下简短的程序说明了修复的区别:

using System;using System.Diagnostics;using System.Collections.Generic;using System.Linq;public class Test

在个人电脑的.NET Framework上,这段代码需要大约7.7秒执行完成。在.NET Core 2.0上,减少到大约0.013s(改进改变了算法的复杂性,集合越大,节省的时间越多)。

或者在SortedSet <T>上考虑这个例子:

public class Test

.NET 4.7中Min和Max的实现遍布SortedSet <T>的整个树,但是只需要找到最小或最大值即可,因为实现可以只遍历相关的节点。PR dotnet / corefx#11968修复了.NET Core实现。在.NET 4.7中,此示例生成如下结果:

00:00:01.1427246 00:00:01.1295220 00:00:01.1350696 00:00:01.1502784 00:00:01.1677880

而在.NET Core 2.0中,我们得到如下结果:

00:00:00.0861391 00:00:00.0861183 00:00:00.0866616 00:00:00.0848434 00:00:00.0860198

显示出相当大的时间下降和吞吐量的增加。

即使像List <T>这样的主工作核心也有改进的空间。考虑下面的例子:

using System;using System.Diagnostics;using System.Collections.Generic;public class Test

在.NET 4.7中,会得到的结果如下:

00:00:00.4434135 00:00:00.4394329 00:00:00.4496867 00:00:00.4496383 00:00:00.4515505

和.NET Core 2.0,得到:

00:00:00.3213094 00:00:00.3211772 00:00:00.3179631 00:00:00.3198449 00:00:00.3164009

可以肯定的是,在0.3秒内可以实现1亿次这样的添加并从列表中删除的操作,这表明操作开始并不慢。但是,通过执行一个应用程序,列表通常会添加到很多,同时也节省了总时间消耗。

这些类型的集合改进扩展不仅仅是System.Collections.Generic命名空间; System.Collections.Concurrent也有很多改进。事实上,.NET Core 2.0上的ConcurrentQueue <T>和ConcurrentBag <T>完全重写了。下面看看一个基本的例子,使用ConcurrentQueue <T>但没有任何并发,例子中使用ConcurrentQueue <T>代替了Queue<T>:

using System;using System.Diagnostics;using System.Collections.Concurrent;public class Test

在个人电脑上,.NET 4.7产生的输出如下:

00:00:02.6485174 00:00:02.6144919 00:00:02.6699958 00:00:02.6441047 00:00:02.6255135

显然,.NET 4.7上的ConcurrentQueue <T>示例比.NET 4.7中的Queue <T>版本慢,因为ConcurrentQueue <T>需要采用同步来确保是否安全使用。但是,更有趣的比较是当在.NET Core 2.0上运行相同的代码时会发生什么:

00:00:01.7700190 00:00:01.8324078 00:00:01.7552966 00:00:01.7518632 00:00:01.7560811

这表明当将.NET Core 2.0切换到30%时,ConcurrentQueue <T>的吞吐量没有任何并发性提高。但是实施中的变化提高了序列化的吞吐量,甚至更多地减少了使用队列的生产和消耗之间的同步,这可能对吞吐量有更明显的影响。请考虑以下代码:

using System;using System.Diagnostics;using System.Collections.Concurrent;using System.Threading.Tasks;public class Test

在.NET 4.7中,个人计算机输出如下结果:

00:00:06.1366044 00:00:05.7169339 00:00:06.3870274 00:00:05.5487718 00:00:06.6069291

而使用.NET Core 2.0,会得到以下结果:

00:00:01.2052460 00:00:01.5269184 00:00:01.4638793 00:00:01.4963922 00:00:01.4927520

这是一个3.5倍的吞吐量的增长。不但CPU效率提高了, 而且内存分配也大大减少。下面的例子主要观察GC集合的数量,而不是挂钟时间:

using System.Diagnostics;using System.Collections.Concurrent;public class Test

在.NET 4.7中,得到以下输出:

Gen0 = 162 Gen1 = 80 Gen2 = 0 Gen0 = 162 Gen1 = 81 Gen2 = 0 Gen0 = 162 Gen1 = 81 Gen2 = 0 Gen0 = 162 Gen1 = 81 Gen2 = 0 Gen0 = 162 Gen1 = 81 Gen2 = 0

而使用.NET Core 2.0,会得到如下输出:

Gen0 = 0 Gen1 = 0 Gen2 = 0 Gen0 = 0 Gen1 = 0 Gen2 = 0 Gen0 = 0 Gen1 = 0 Gen2 = 0 Gen0 = 0 Gen1 = 0 Gen2 = 0 Gen0 = 0 Gen1 = 0 Gen2 = 0

.NET 4.7中的实现使用了固定大小的数组链表,一旦固定数量的元素被添加到每个数组中,就会被丢弃, 这有助于简化实现,但也会导致生成大量垃圾。在.NET Core 2.0中,新的实现仍然使用链接在一起的链接列表,但是随着新的片段的添加,这些片段的大小会增加,更重要的是使用循环缓冲区,只有在前一个片段完全结束时,新片段才会增加。这种分配的减少可能对应用程序的整体性能产生相当大的影响。

ConcurrentBag <T>也有类似改进。ConcurrentBag <T>维护thread-local work-stealing队列,使得添加到的每个线程都有自己的队列。在.NET 4.7中,这些队列被实现为每个元素占据一个节点的链接列表,这意味着对该包的任何添加都会导致分配。在.NET Core 2.0中,这些队列是数组,这意味着除了增加阵列所涉及的均摊成本之外,增加的还是无需配置的。以下可以看出:

using System;using System.Diagnostics;using System.Collections.Concurrent;public class Test

在.NET 4.7中,个人计算机上产生以下输出:

Elapsed=00:00:06.5672723 Gen0=953 Gen1=0 Gen2=0 Elapsed=00:00:06.4829793 Gen0=954 Gen1=1 Gen2=0 Elapsed=00:00:06.9008532 Gen0=954 Gen1=0 Gen2=0 Elapsed=00:00:06.6485667 Gen0=953 Gen1=1 Gen2=0 Elapsed=00:00:06.4671746 Gen0=954 Gen1=1 Gen2=0

而使用.NET Core 2.0,会得到:

Elapsed=00:00:04.3377355 Gen0=0 Gen1=0 Gen2=0 Elapsed=00:00:04.2892791 Gen0=0 Gen1=0 Gen2=0 Elapsed=00:00:04.3101593 Gen0=0 Gen1=0 Gen2=0 Elapsed=00:00:04.2652497 Gen0=0 Gen1=0 Gen2=0 Elapsed=00:00:04.2808077 Gen0=0 Gen1=0 Gen2=0

吞吐量提高了约30%,并且分配和完成的垃圾收集量减少了。

LINQ

在应用程序代码中,集合通常与语言集成查询(LINQ)紧密相连,该查询已经有了更多的改进。LINQ中的许多运算符已经完全重写为.NET Core,以便减少分配的数量和大小,降低算法复杂度,并且消除不必要的工作。

例如,Enumerable.Concat方法用于创建一个单一的IEnumerable <T>,它首先产生first域可枚举的所有元素,然后再生成second域所有的元素。它在.NET 4.7中的实现是简单易懂的,下面的代码正好反映了这种行为表述:

static IEnumerable<TSource> ConcatIterator<TSource>(IEnumerable<TSource> first IEnumerable<TSource> second) { foreach (TSource element in first) yield return element; foreach (TSource element in second) yield return element;

当两个序列是简单的枚举,如C#中的迭代器生成的,这种过程会执行的很好。但是如果应用程序代码具有如下代码呢?

first.Concat(second.Concat(third.Concat(fourth)));

每次我们从迭代器中退出时,则会返回到枚举器的MoveNext方法。这意味着如果你从另一个迭代器中枚举产生一个元素,则会返回两个MoveNext方法,并移动到下一个需要调用这两个MoveNext方法的元素。你调用的枚举器越多,操作所需的时间越长,特别是这些操作中的每一个都涉及多个接口调用(MoveNext和Current)。这意味着连接多个枚举会以指数方式增长,而不是呈线性增长。PR dotnet / corefx#6131修正了这个问题,在下面的例子中,区别是显而易见的:

using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;public class Test

在个人计算机上,.NET 4.7需要大约4.12秒。但在.NET Core 2.0中,这只需要约0.14秒,提高了30倍。

通过消除多个运算器同时使用时的消耗,运算器也得到了大大的提升。例如下面的例子:

using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;public class Test

在这里,我们创建一个可以从10 000 000下降到0的数字,然后再等待一会来排序它们上升,跳过排序结果中的前4个元素,并抓住第五个。在个人计算机上的NET 4.7中得到如下输出:

00:00:01.3879042 00:00:01.3438509 00:00:01.4141820 00:00:01.4248908 00:00:01.3548279

而使用.NET Core 2.0,会得到如下输出:

00:00:00.1776617 00:00:00.1787467 00:00:00.1754809 00:00:00.1765863 00:00:00.1735489

这是一个巨大的改进(〜8x),避免了大部分的开销。

类似地,来自justinvp的 PR dotnet / corefx#3429对常用的ToList方法添加了优化,为已知长度的源,提供了优化的路径,并且通过像Select这样的操作器来管理。在以下简单测试中,这种影响是显而易见的:

using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;public class Test

在.NET 4.7中,会得到如下结果:

00:00:00.1308687 00:00:00.1228546 00:00:00.1268445 00:00:00.1247647 00:00:00.1503511

而在.NET Core 2.0中,得到如下结果:

00:00:00.0386857 00:00:00.0337234 00:00:00.0346344 00:00:00.0345419 00:00:00.0355355

显示吞吐量增加约4倍。

在其他情况下,性能优势来自于简化实施,以避免开销,例如减少分配,避免委托分配,避免接口调用,最小化字段读取和写入,避免拷贝等。例如,jamesqo为PR dotnet / corefx#11208做出的贡献,大大地减少了Enumerable.ToArray涉及的开销。请看下面的例子:

using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;public class Test

在.NET 4.7中,会得到如下的结果:

Elapsed=00:00:01.0548794 Gen0=2 Gen1=2 Gen2=2 Elapsed=00:00:01.1147146 Gen0=2 Gen1=2 Gen2=2 Elapsed=00:00:01.0709146 Gen0=2 Gen1=2 Gen2=2 Elapsed=00:00:01.0706030 Gen0=2 Gen1=2 Gen2=2 Elapsed=00:00:01.0620943 Gen0=2 Gen1=2 Gen2=2

而.NET Core 2.0的结果如下:

Elapsed=00:00:00.1716550 Gen0=1 Gen1=1 Gen2=1 Elapsed=00:00:00.1720829 Gen0=1 Gen1=1 Gen2=1 Elapsed=00:00:00.1717145 Gen0=1 Gen1=1 Gen2=1 Elapsed=00:00:00.1713335 Gen0=1 Gen1=1 Gen2=1 Elapsed=00:00:00.1705285 Gen0=1 Gen1=1 Gen2=1

这个例子中提高了6倍,但是垃圾收集却只有一半。

LINQ有一百多个运算器,本文只提到了几个,其它的很多也都有所改进。

压缩

前面所展示的集合和LINQ的例子都是处理内存中的数据,当然还有许多其他形式的数据处理,包括大量CPU计算和逻辑判断,这些运算也在得到提升。

一个关键的例子是压缩,例如使用DeflateStream,性能方面也有一些重大的性能改进。例如,在.NET 4.7中,zlib(本地压缩库)用于压缩数据,但是相对未优化的托管实现了用于解压缩的数据; PR dotnet / corefx#2906添加了.NET Core支持,以便使用zlib进行解压缩。来自bjjones的 PR dotnet / corefx#5674使用英特尔生产的zlib这个更优化的版本。这些结合产生了非常棒的效果。下面的例子,创建一个大量的数据:

using System;using System.IO;using System.IO.Compression;using System.Diagnostics;public class Test

在.NET 4.7中,这一个压缩/解压缩操作,会得到如下结果:

00:00:00.7977190

而使用.NET Core 2.0,会得到如下结果:

00:00:00.1926701加密

.NET应用程序中另一个常见的计算源是使用加密操作,在这方面.NET Core也有改进。例如,在.NET 4.7中,SHA256.Create返回在管理代码中实现的SHA256类型,而管理代码可以运行得非常快,但是对于运算量非常大的计算,这仍然难以与原始吞吐量和编译器优化竞争。相反,对于.NET Core 2.0,SHA256.Create返回基于底层操作系统的实现,例如在Windows上使用CNG或在Unix上使用OpenSSL。从下面这个简单的例子可以看出,它散列着一个100MB的字节数组:

using System;using System.Diagnostics;using System.Security.Cryptography;public class Test

在.NET 4.7中,会得到:

00:00:00.7576808

而使用.NET Core 2.0,会得到:

00:00:00.4032290

零代码更改的一个很好提升。

数学运算

数学运算也是一个很大的计算量,特别是处理大量数据时。通过像dotnet / corefx#2182这样的PR ,axelheer对BigInteger的各种操作做了一些实质的改进。请考虑以下示例:

using System;using System.Diagnostics;using System.Numerics;public class Test

在.NET 4.7中,会得到以下输出结果:

00:00:05.6024158

.NET Core 2.0上的相同代码会得到输出结果如下:

00:00:01.2707089

这是开发人员只关注.NET的某个特定领域的一个很好的例子,开发人员使得这种改进更好的满足了自己的需求,同时也满足了可能会用到这方面功能的其他开发人员的需求。

一些核心的整型类型的数学运算也得到了改进。例如:

using System;using System.Diagnostics;public class Test

PR dotnet / coreclr#8125用更快的实现取代了DivRem,在.NET 4.7中会得到的如下结果:

00:00:01.4143100

并在.NET Core 2.0上得到如下结果:

00:00:00.7469733

吞吐量提高约2倍。

未完待续... ...

转载请注明出自:葡萄城控件

关于葡萄城

葡萄城是全球控件行业领导者,世界领先的企业应用定制工具、企业报表和商业智能解决方案提供商,为超过75%的全球财富500强企业提供服务。

猜您喜欢: