在.NET Core 3.0中,超过1000种新的硬件内置方法被添加并被JIT识别,从而使c#代码能够直接针对指令集,如SSE4和AVX2(docs)。然后,在核心库中的一组api中使用了这些工具。但是,intrinsic仅限于x86/x64架构。在.NET 5中,我们投入了大量的精力来增加数千个组件,特别是针对ARM64,这要感谢众多贡献者,特别是来自Arm Holdings的@TamarChristinaArm。与对应的x86/x64一样,这些内含物在核心库功能中得到了很好的利用。例如,BitOperations.PopCount()方法之前被优化为使用x86 POPCNT内在的,对于.NET 5, dotnet/runtime#35636 增强了它,使它也能够使用ARM VCNT或等价的ARM64 CNT。类似地,dotnet/runtime#34486修改了位操作。LeadingZeroCount, TrailingZeroCount和Log2利用相应的instrincs。在更高的级别上,来自@Gnbrkm41的dotnet/runtime#33749增强了位数组中的多个方法,以使用ARM64内含物来配合之前添加的对SSE2和AVX2的支持。为了确保Vector api在ARM64上也能很好地执行,我们做了很多工作,比如dotnet/runtime#33749和dotnet/runtime#36156。
除ARM64之外,还进行了其他工作以向量化更多操作。 例如,@Gnbrkm41还提交了dotnet/runtime#31993,该文件利用x64上的ROUNDPS / ROUNDPD和ARM64上的FRINPT / FRINTM来改进为新Vector.Ceiling和Vector.Floor方法生成的代码。 BitOperations(这是一种相对低级的类型,针对大多数操作以最合适的硬件内部函数的1:1包装器的形式实现),不仅在@saucecontrol 的dotnet/runtime#35650中得到了改进,而且在Corelib中的使用也得到了改进 更有效率。
最后,JIT进行了大量的修改,以更好地处理硬件内部特性和向量化,比如dotnet/runtime#35421, dotnet/runtime#31834, dotnet/runtime#1280, dotnet/runtime#35857, dotnet/runtime#36267和 dotnet/runtime#35525。
GC和JIT代表了运行时的大部分,但是在运行时中这些组件之外仍然有相当一部分功能,并且这些功能也有类似的改进。
有趣的是,JIT不会为所有东西从头生成代码。JIT在很多地方调用了预先存在的 helpers函数,运行时提供这些 helpers,对这些 helpers的改进可以对程序产生有意义的影响。dotnet/runtime#23548 是一个很好的例子。在像System这样的图书馆中。Linq,我们避免为协变接口添加额外的类型检查,因为它们的开销比普通接口高得多。本质上,dotnet/runtime#23548 (随后在dotnet/runtime#34427中进行了调整)增加了一个缓存,这样这些数据转换的代价被平摊,最终总体上更快了。这从一个简单的微基准测试中就可以明显看出:
IsIReadOnlyCollection .NET FW 4.8 105.460 ns 1.00 53 B
IsIReadOnlyCollection .NET Core 3.1 56.252 ns 0.53 59 B
IsIReadOnlyCollection .NET 5.0 3.383 ns 0.03 45 B