文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-11-30 05:50

关注

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, IEnumerable, ICloneable, IComparable, IComparable, IConvertible, IEquatable
{
  //这里代码省略
}

JIT会判断类型是否是sealed标志,如果是则不进行安全性检查优化。

虽然.Net8去掉了CORINFO_HELP_LDELEMA_REF,

但是多了范围的检查CORINFO_HELP_RNGCHKFAIL,那它这个性能如何呢?

我们来测试下:

dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0

结果是:

Method

Runtime

Mean

Ratio

Code Size

Get2

.NET 7.0

0537 ns

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(_o);


    [MethodImpl(MethodImplOptions.NoInlining)]
    public T Cast(object o) => (T)o;
}

同样的

dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0

结果如下:

Method

Runtime

Mean

Ratio

GetString

.NET 7.0

018 ns

00

GetString

.NET 8.0

198 ns

0.40

.Net8是三倍于.Net7的运行速度。去掉类型检查+类型转换的内联,大幅度的提升效率,可见.Net8的性能优化确实不容小觑。

参考如下:

https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/

最后推荐下个人的CLR/JIT交流圈,里面有多篇个人编写的高质量的原创栏目和文章。学习心得,项目经验等。带你进入.Net核心技术阶层,脱离curd工程师范畴。

来源:江湖评谈内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯