【翻译】.NET 5中的性能改进 (7)


注意.NET5的运行速度不仅比.NET Core 3.1快15%,我们还可以看到它的汇编代码大小小了22%(额外的“Code Size”一栏来自于我在benchmark类中添加了[DisassemblyDiagnoser])。
另一个很好的边界检查移除来自dotnet/runtime#36263中的@nathan-moore。我提到过,JIT已经能够删除非常常见的从0迭代到数组、字符串或span长度的模式的边界检查,但是在此基础上还有一些比较常见的变化,但以前没有认识到。例如,考虑这个微基准测试,它调用一个方法来检测一段整数是否被排序:

private int[] _array = Enumerable.Range(0, 1000).ToArray(); [Benchmark] public bool IsSorted() => IsSorted(_array); private static bool IsSorted(ReadOnlySpan<int> span) { for (int i = 0; i < span.Length - 1; i++) if (span[i] > span[i + 1]) return false; return true; }


这种与以前识别的模式的微小变化足以防止JIT忽略边界检查。现在不是了.NET5在我的机器上可以快20%的执行:

Method Runtime Mean Ratio Code Size
IsSorted   .NET FW 4.8   1,083.8 ns   1.00   236 B  
IsSorted   .NET Core 3.1   581.2 ns   0.54   136 B  
IsSorted   .NET 5.0   463.0 ns   0.43   105 B  


JIT确保对某个错误类别进行检查的另一种情况是空检查。JIT与运行时协同完成这一任务,JIT确保有适当的指令来引发硬件异常,然后与运行时一起将这些错误转换为.NET异常())。但有时指令只用于null检查,而不是完成其他必要的功能,而且只要需要的null检查是由于某些指令发生的,不必要的重复指令可以被删除。考虑这段代码:

private (int i, int j) _value; [Benchmark] public int NullCheck() => _value.j++;


作为一个可运行的基准测试,它所做的工作太少,无法用基准测试进行准确的度量.NET,但这是查看生成的汇编代码的好方法。在.NET Core 3.1中,此方法产生如下assembly:

; Program.NullCheck() nop dword ptr [rax+rax] cmp [rcx],ecx add rcx,8 add rcx,4 mov eax,[rcx] lea edx,[rax+1] mov [rcx],edx ret ; Total bytes of code 23

cmp [rcx],ecx指令在计算j的地址时执行null检查,然后mov eax,[rcx]指令执行另一个null检查,作为取消引用j的位置的一部分。因此,第一个null检查实际上是不必要的,因为该指令没有提供任何其他好处。所以,多亏了像dotnet/runtime#1735和dotnet/runtime#32641这样的PRs,这样的重复被JIT比以前更多地识别,对于.NET 5,我们现在得到了:

; Program.NullCheck() add rcx,0C mov eax,[rcx] lea edx,[rax+1] mov [rcx],edx ret ; Total bytes of code 12

协方差是JIT需要注入检查以确保开发人员不会意外地破坏类型或内存安全性的另一种情况。考虑一下代码

class A { } class B { } object[] arr = ...; arr[0] = new A();

这个代码有效吗?视情况而定。.NET中的数组是“协变”的,这意味着我可以传递一个数组派生类型[]作为BaseType[],其中派生类型派生自BaseType。这意味着在本例中,arr可以被构造为新A[1]或新对象[1]或新B[1]。这段代码应该在前两个中运行良好,但如果arr实际上是一个B[],试图存储一个实例到其中必须失败;否则,使用数组作为B[]的代码可能尝试使用B[0]作为B,事情可能很快就会变得很糟糕。因此,运行时需要通过协方差检查来防止这种情况发生,这实际上意味着当引用类型实例存储到数组中时,运行时需要检查所分配的类型实际上与数组的具体类型兼容。使用dotnet/runtime#189, JIT现在能够消除更多的协方差检查,特别是在数组的元素类型是密封的情况下,比如string。因此,像这样的微基准现在运行得更快了:

private string[] _array = new string[1000]; [Benchmark] public void CovariantChecking() { string[] array = _array; for (int i = 0; i < array.Length; i++) array[i] = "default"; } Method Runtime Mean Ratio Code Size
CovariantChecking   .NET FW 4.8   2.121 us   1.00   57 B  
CovariantChecking   .NET Core 3.1   2.122 us   1.00   57 B  
CovariantChecking   .NET 5.0   1.666 us   0.79   52 B  

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsssyf.html