.Net8顶级性能优化:类型转换

发表于:2023-11-07 09:40

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:江湖    来源:江湖评谈

  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()
         sub       rsp,28
         mov       rcx,[rcx+8]
         xor       edx,edx
         mov       r8,offset MT_System.String
         call      CORINFO_HELP_LDELEMA_REF
         mov       rax,[rax]
         add       rsp,28
         ret
  ; Total bytes of code 33
  .Net7它这里有一个CORINFO_HELP_LDELEMA_REF进行安全性检查。
  .Net8:
  ; Tests.Get2()
         sub       rsp,28
         mov       rax,[rcx+8]
         cmp       dword ptr [rax+8],0
         jbe       short M00_L00
         mov       rax,[rax+10]
         add       rsp,28
         ret
  M00_L00:
         call      CORINFO_HELP_RNGCHKFAIL
         int       3
  ; 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,
  但是多了范围的检查CORINFO_HELP_RNGCHKFAIL,那它这个性能如何呢?
  我们来测试下:
  dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0
  结果是:
  我们看到同样代码,.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
  结果如下:
  .Net8是三倍于.Net7的运行速度。去掉类型检查+类型转换的内联,大幅度的提升效率,可见.Net8的性能优化确实不容小觑。
  参考如下:
  https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/
  最后推荐下个人的CLR/JIT交流圈,里面有多篇个人编写的高质量的原创栏目和文章。学习心得,项目经验等。带你进入.Net核心技术阶层,脱离curd工程师范畴。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号