性能是.NET Core的一个关键特性
關鍵要點
.NET Core是跨平臺的,可運行在Windows、Linux、Mac OS X和更多平臺上;與.NET相比,發布周期要短得多。大多數.NET Core 都是通過NuGet軟件包交付的,可以很容易地發布和升級。
更快速的發布周期對性能提升工作以及改進諸如SortedSet和LINQ . tolist()方法等語言結構性能的大量工作都有著特別的幫助。
通過引入了System.ValueTuple和Span這樣的類型,更快的周期和更容易的升級也為迭代改進 .NET Core性能的新想法帶來了機會。
這些改進之后可以反饋到完整的 .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框架的一部分。
現在讓我們來看看其中的一些性能和內存改進。
.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", 33);}
//之后:
private (string, int) GetNameAndAge(){return ("Maarten", 33);}
在第一個例子中,我們分配一個元組。因為幾乎不必做什么額外的工作,分配是在托管堆上完成的,在某個時刻,垃圾回收(GC)將不得不清理它。在第二種情況下,編譯器生成的代碼使用的是ValueTuple類型,它本身就是一個struct,并在堆棧上創建,這使我們能夠訪問我們想要處理的兩個值,同時確保在包含的數據結構上不需要做垃圾回收。
如果我們使用ReSharper的中間語言(IL)查看器查看編譯器在上面示例中生成的代碼,那么就會很明顯看到這種差異。這里有兩個方法簽名:
//之前:.methodprivate hidebysig instance class [System.Runtime]System.Tuple`2<string, int32> ? GetNameAndAge() cil managed {// ...}//之后:.method
private hidebysig instance valuetype [System.Runtime]System.ValueTuple`2<string, int32> GetNameAndAge() cil managed {// ...}
我們可以清楚地看到第一個示例返回一個類的實例,第二個例子返回一個值類型的實例。類是在托管堆中分配的(由CLR跟蹤和管理,并受垃圾收集的管制,是可變的),而值類型分配在堆棧上(速度快且較少的開銷,是不可變的)。簡而言之: System.ValueTuple本身并沒有被CLR跟蹤,它只是作為我們關心的嵌入值的一個簡單容器。
請注意,在其優化的內存使用情況下,像元組解構這樣的特性是非常令人愉快的副產品,它使這部分語言和框架都成為了這一部分。
使用Span<T>減少子字符串的內存分配
在前一節中,我們已經討論了棧和托管堆。大多數.NET開發人員只使用托管堆,但.NET有三種類型的內存可供使用,這取決于具體情況:
棧內存——我們通常分配的值類型的內存空間,比如int, double, bool,……它非常快(通常在CPU的緩存中使用),但大小有限(通常小于1 MB)。富有挑戰精神的開發人員會使用stackalloc關鍵字添加自定義對象,但要知道它們是有危險性的,因為在任何時間都可能發生StackOverflowException,使我們的整個應用程序崩潰。
非托管內存——沒有垃圾收集器的內存空間,我們必須自己使用像Marshal.AllocHGlobal 和Marshal.FreeHGlobal之類的方法預訂和釋放內存。
托管內存/托管堆——垃圾收集器釋放已經不再使用的內存空間,使我們大多數人都過著無憂無慮的程序員生活,很少有內存問題。
它們都有各自的優缺點,并有特定的用例。但是,如果我們想要編寫一個與所有這些內存類型兼容的庫該怎么辦呢? 我們必須分別為他們提供方法。一個針對托管對象,另一個針對指針指向堆棧上或非托管堆上的對象。一個很好的例子就是創建一個字符串的子字符串。我們需要獲取一個System.String并返回一個新System.String的方法,即要處理的托管版本的子字符串。非托管/堆棧版本將使用char*(是的,一個指針!)和字符串的長度,并返回類似的指向結果的指針。難以控制…
這個System.Memory NuGet包(目前仍是預覽版)引入了一個新的Span<T>結構。它是一個值類型(因此沒有被垃圾收集器跟蹤),它試圖統一對任何底層內存類型的訪問。它提供了一些方法,但本質上是這樣的:
一個T的引用
一個可選的開始索引
一個可選的長度
一些實用函數可以抓取一個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 = 0; 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請求中獲取請求體。我們必須分配具有重復數據的大塊內存:一個具有整個傳入請求的內存和一個僅包含請求體的子字符串。然后是需要從原始字符串復制數據到子字符串的開銷。
現在,讓我們用 (ReadOnly)Span<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的性能前景光明!
關于作者
Maarten Balliauw喜歡構建web和云應用程序。他的主要興趣是ASP.NET MVC、 c#、Microsoft Azure、 PHP和應用程序性能。他與別人共同創立了MyGet,他還是JetBrains的開發人員。他是微軟Azure的ASP內部人員和MVP。Maarten在不同的國家和國際活動中經常發言,并在比利時組織Azure用戶組活動。在業余時間,他自己釀造啤酒。Maarten的博客。
?
隨著.NET Core2.0的發布,微軟有了下一個主要版本的通用目標,模塊化、跨平臺和開源平臺最初發布于2016年。.NET Core 已經創建了許多api,這些api可以在.NET 框架的當前版本中使用。它最初是為下一代ASP.NET創建的解決方案,但現在是驅動、是許多其他場景的基礎,包括物聯網、云和下一代移動解決方案。在本系列中,我們將探討一些.NET Core的好處,以及它如何不僅能讓傳統的.NET開發人員受益,還能讓所有需要為市場帶來健壯、高性能和經濟解決方案的技術人員受益。
相關文章:
Azure和.NET Core成就天作之合
.Net Core中使用ref和Span<T>提高程序性能
原文地址: http://www.infoq.com/cn/articles/performance-net-core
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
總結
以上是生活随笔為你收集整理的性能是.NET Core的一个关键特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Core 跨平台执行命令、脚本
- 下一篇: 将 ASP.NET Core 2.0 项