1.前言.NET8通过各种骚操 , 把性能提升到了前所未有的高度 。超越以往任何版本,也涵盖了后续版本,比如.NET9或许可能没有如此大的性能优化了 。本篇来看下它其中的一个优化:类型转换的优化效果 。
2.示例通过类型检查的优化,优化掉某些情况下类型转换的时候JIT类型检查的函数 。下面的代码是类型检查的典型应用 。
[HideColumns("Error", "StdDev", "Median", "RatIOSD")][DisassemblyDiagnoser(maxDepth: 0)]public class Tests{private readonly string[] _strings = new string[1];[Benchmark]public string Get1() => _strings[0];[Benchmark]public string Get2() => Volatile.Read(ref _strings[0]);}public partial class Program{static void MAIn(string[] args){BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);}}
我们看到_strings是个私有数组,Get1函数中获取_strings数组的第一个值 。所以它是直接用ldelem.ref IL执行即可
ldelem.ref
但是Get2里面对数组元素进行了引用,所以Roslyn的指令是:
ldelema [System.Runtime]System.String
如果ref类型的变量,被赋值为不同于这个变量的类型则会违反类型安全性 。通常情况下ldelema需要进行类型检查,也就是用JIT辅助函数CORINFO_HELP_LDELEMA_REF来进行检查,以确保不会违反类型安全性 。
这个安全性的检查会极大损耗性能,.NET8的JIT进行了一个优化 , 思路是如果是sealed关键字标记的类型 , 就不会进行安全性检查,这样就会提高性能 。为什么sealed不会呢?
这其实是利用了它的一个特性,就是不会被继承 。不会被继承,就不会被子类的类型所困扰,只有string一个类型,自然不会用以进行类型检查了 。
这是第一点优化,下面看下 。
3.第一阶优化优化了类型安全检查,缩短了编译时间,提高了性能 。来看下.Net7和.NET8的生成Get2函数的的不同点
.Net7:
Tests.Get2()subrsp,28movrcx,[rcx+8]xoredx,edxmovr8,offset MT_System.StringcallCORINFO_HELP_LDELEMA_REFmovrax,[rax]addrsp,28ret; Total bytes of code 33
.Net7它这里有一个CORINFO_HELP_LDELEMA_REF进行安全性检查 。
.Net8:
; Tests.Get2()subrsp,28movrax,[rcx+8]cmpdword ptr [rax+8],0jbeshort M00_L00movrax,[rax+10]addrsp,28retM00_L00:callCORINFO_HELP_RNGCHKFAILint3; Total bytes of code 29
.Net8里它没有了CORINFO_HELP_LDELEMA_REF
因为string类型是sealed,它的原型如下:
public sealed class String : IEnumerable<char>, IEnumerable, ICloneable, IComparable, IComparable<String?>, IConvertible, IEquatable<String?>{//这里代码省略}
JIT会判断类型是否是sealed标志,如果是则不进行安全性检查优化 。
虽然.Net8去掉了CORINFO_HELP_LDELEMA_REF,
【.Net8顶级性能优化:类型转换】但是多了范围的检查CORINFO_HELP_RNGCHKFAIL,那它这个性能如何呢?
我们来测试下:
dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0
结果是:
Runtime
Mean
Ratio
Code Size
Get2
.NET 7.0
1.0537 ns
1.00
33 B
Get2
.NET 8.0
0.2423 ns
0.23
29 B
我们看到同样代码,.Net8里面比.Net7的性能提升了5倍之多 。
4.第二阶优化承接上面 , 上面sealed去掉了类型检查 。
然后在类型转换的时候,一般的类型转换JIT使用的是CastHelpers.ChkCastAny来进行 。
但是.Net8里面内联了一个方法
用以缩短CastHelpers.ChkCastAny的编译时间,提高编译的时间和程序的性能 。
using BenchmarkDotNet.Attributes;using BenchmarkDotNet.Running;using System.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);[HideColumns("Error", "StdDev", "Median", "RatioSD")]public class Tests{private readonly object _o = "hello";[Benchmark]public string GetString() => Cast<string>(_o);[MethodImpl(MethodImplOptions.NoInlining)]public T Cast<T>(object o) => (T)o;}
同样的dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0
结果如下:推荐阅读
- 苹果a13处理器性能,a13处理器还能用几年
- 顶级女神下海,曝光这个全球大丑闻
- 她是顶级富二代的亲妈,也是张国荣好友,替前夫还债后嫁给债主,如今身家亿万
- 顶级妈宝男:所有姻缘全被亲妈拆散,44岁无人敢嫁难过到崩溃大哭
- Redis管道技术瞬间提升系统性能,速度翻倍!
- 掌握Nginx的高级用法,构建高性能Web应用
- Baby近况很不妙?曝团队不开工找下家或四分五裂,顶级资源恐流失
- 日媒曝石川佳纯身价200万不逊顶级女演员 与中年男子聚会遭跟拍
- cpu占用率高的解决方法,提升你的电脑性能
- JDK21 性能提升 20 倍