与此相关的是类型检查。我之前提到过Span解决了很多问题,但也引入了新的模式,从而推动了系统其他领域的改进;对于Span本身的实现也是这样。 Span 构造函数做协方差检查,要求T[]实际上是T[]而不是U[],其中U源自T,例如:
PR dotnet/runtime#32790就是这样优化数组的.GetType()!= typeof(T [])检查何时密封T,而dotnet/runtime#1157识别typeof(T).IsValueType模式并将其替换为常量 值(PR dotnet/runtime#1195对于typeof(T1).IsAssignableFrom(typeof(T2))进行了相同的操作)。 这样做的最终结果是极大地改善了微基准,例如:
class A { } sealed class B : A { } private B[] _array = new B[42]; [Benchmark] public int Ctor() => new Span<B>(_array).Length;我得到的结果如下:
Method Runtime Mean Ratio Code SizeCtor .NET FW 4.8 48.8670 ns 1.00 66 B
Ctor .NET Core 3.1 7.6695 ns 0.16 66 B
Ctor .NET 5.0 0.4959 ns 0.01 17 B
当查看生成的程序集时,差异的解释就很明显了,即使不是完全精通程序集代码。以下是[DisassemblyDiagnoser]在.NET Core 3.1上生成的内容:
下面是.NET5的内容:
; Program.Ctor() mov rax,[rcx+8] test rax,rax jne short M00_L00 xor eax,eax jmp short M00_L01 M00_L00: mov eax,[rax+8] M00_L01: ret ; Total bytes of code 17另一个例子是,在前面的GC讨论中,我提到了将本地运行时代码移植到c#代码中所带来的一些好处。有一点我之前没有提到,但现在将会提到,那就是它导致了我们对系统进行了其他改进,解决了移植的关键阻滞剂,但也改善了许多其他情况。一个很好的例子是dotnet/runtime#38229。当我们第一次将本机数组排序实现移动到managed时,我们无意中导致了浮点值的回归,这个回归被@nietras 发现,随后在dotnet/runtime#37941中修复。回归是由于本机实现使用一个特殊的优化,我们失踪的管理端口(浮点数组,将所有NaN值数组的开始,后续的比较操作可以忽略NaN)的可能性,我们成功了。然而,问题是这个的方式表达并没有导致大量的代码重复:本机实现模板,使用和管理实现使用泛型,但限制与泛型等,内联 helpers介绍,以避免大量的代码重复导致non-inlineable在每个比较采用那种方法调用。PR dotnet/runtime#38229通过允许JIT在同一类型内嵌共享泛型代码解决了这个问题。考虑一下这个微基准测试:
private C c1 = new C() { Value = 1 }, c2 = new C() { Value = 2 }, c3 = new C() { Value = 3 }; [Benchmark] public int Compare() => Comparer<C>.Smallest(c1, c2, c3); class Comparer<T> where T : IComparable<T> { public static int Smallest(T t1, T t2, T t3) => Compare(t1, t2) <= 0 ? (Compare(t1, t3) <= 0 ? 0 : 2) : (Compare(t2, t3) <= 0 ? 1 : 2); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Compare(T t1, T t2) => t1.CompareTo(t2); } class C : IComparable<C> { public int Value; public int CompareTo(C other) => other is null ? 1 : Value.CompareTo(other.Value); }最小的方法比较提供的三个值并返回最小值的索引。它是泛型类型上的一个方法,它调用同一类型上的另一个方法,这个方法反过来调用泛型类型参数实例上的方法。由于基准使用C作为泛型类型,而且C是引用类型,所以JIT不会专门为C专门化此方法的代码,而是使用它生成的用于所有引用类型的“shared”实现。为了让Compare方法随后调用到CompareTo的正确接口实现,共享泛型实现使用了一个从泛型类型映射到正确目标的字典。在. net的早期版本中,包含那些通用字典查找的方法是不可行的,这意味着这个最小的方法不能内联它所做的三个比较调用,即使Compare被归为methodimploptions .侵略化的内联。前面提到的PR消除了这个限制,在这个例子中产生了一个非常可测量的加速(并使数组排序回归修复可行):
Method Runtime Mean RatioCompare .NET FW 4.8 8.632 ns 1.00
Compare .NET Core 3.1 9.259 ns 1.07
Compare .NET 5.0 5.282 ns 0.61