性能是.Net Core一個很是關鍵的特性,今天咱們重點研究一下ValueTuple<T>和Span<T>.程序員
1、方法的多個返回值的實現,看ValueTuple<T>跨域
平常開發中,假如咱們一個方法有多個返回值,咱們可能會用Out出參,或者使用一個自定義類/匿名類型,或者Tuple<T>. 數組
如今咱們看看ValueTuple<T>的實現緩存
C# 7支持返回多個值的語言特性,咱們寫兩個示例代碼Tuple<T>和ValueTuple<T>,對比一下:數據結構
1 /// <summary> 2 /// Tuple 3 /// </summary> 4 /// <returns></returns> 5 private Tuple<string, List<int>> GetValues() 6 { 7 return new Tuple<string, List<int>>("C7", new List<int> { 1, 2, 3 }); 8 } 9 10 /// <summary> 11 /// ValueTuple 12 /// </summary> 13 /// <returns></returns> 14 private (string, List<int>) GetValuesN() 15 { 16 return ("C7", new List<int> { 1, 2, 3 }); 17 }
Tuple的示例中,代碼聲明瞭一個Tuple元組,內存在託管堆上統一管理,GC垃圾回收在指定時機下回收。函數
ValueTuple示例中,編譯器生成的代碼使用的是ValueTuple,其自己就是一個struct,在棧上建立,這使咱們既能夠訪問這個返回值數據,同時確保在包含的數據結構上不須要作垃圾回收。性能
咱們經過IL Spy看下編譯後的代碼:this
上圖能夠看到:編碼
第一個方法GetValues,返回class [System.Runtime]System.Tuple`2<string, class [System.Collections]System.Collections.Generic.List`1<int32>>,一個類的實例 lua
第二個方法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支持方法多返回值,的確在底層實現上考慮了性能表現(內存),同時編碼上給咱們帶來了更愉快的語法特性!
2、從字符串操做看Span<T>
大多數.Net開發場景,只使用到了託管堆(由CLR統一管理),其實.Net 有三種類型的內存能夠使用,不過要看具體的使用場景。
上述三種類型的內存,都有各自的優缺點,特色的使用場景。若是咱們設計一個兼容支持上述三種類型的Lib,須要分別提供兩種實現,一種是支持託管堆的,一種是支持棧和非託管內存的。好比說SubString。
咱們先看第一種支持託管推的SubString實現:
1 string Substring(string source, int startIndex, int length) 2 { 3 var result = new char[length]; 4 for (var i = 0; i < length; i++) 5 { 6 result[i] = source[startIndex + i]; 7 } 8 9 return new string(result); 10 }
上述方法內部聲明瞭新的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 >= 0); _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