性能是.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