.NET8 极致性能优化 CHRL(CORINFO_HELP_RNGCHKFAIL)

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: .NET8 在 .NET7 的基础上进行了进一步的优化,比如 CHRL (全称:CORINFO_HELP_RNGCHKFAIL)优化技术,它是边界检查,在 .NET7 里面它已经进行了部分优化,但是 .NET8 里面它继续优化,类似人工智能,.NET8 能意识到某些性能问题,从而进行优化。

前言

.NET8.NET7 的基础上进行了进一步的优化,比如 CHRL (全称:CORINFO_HELP_RNGCHKFAIL)优化技术,CORINFO_HELP_RNGCHKFAIL 是边界检查,在 .NET7 里面它已经进行了部分优化,但是 .NET8 里面它继续优化,类似人工智能,.NET8 能意识到某些性能问题,从而进行优化。

概述

JIT 会对数组,字符串的范围边界进行检查。比如数组的索引是否在数组长度范围内,不能超过。所以 JIT 就会产生边界检查的步骤。


public class Tests
{
   
    private byte[] _array = new byte[8];
    private int _index = 4;

    public void Get() => Get(_array, _index);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static byte Get(byte[] array, int index) => array[index];
}

Get 函数 .NET7ASM 如下:

; Tests.Get(Byte[], Int32)
       sub       rsp,28
       cmp       edx,[rcx+8]
       jae       short M01_L00
       mov       eax,edx
       movzx     eax,byte ptr [rcx+rax+10]
       add       rsp,28
       ret
M01_L00:
       call      CORINFO_HELP_RNGCHKFAIL
       int       3

cmp 指令把数组的 MT(方法表) 偏移 8 位置的数组长度与当前的数组索引对比,两者如果索引大于(后者)或等于(jae)数组长度(前者)的时候。就会跳转到 CORINFO_HELP_RNGCHKFAIL 进行边界检查,可能会引发超出引范围的异常 IndexOutOfRangeException。但是实际上这段这段代码的访问只需要两个 mov,一个是数组的索引,一个是(MT+0x10+索引)取其值返回即可。所以这个地方有清晰可见的优化的地方。

.NET8 学习了一些范围边界的智能化优化,也就说,有的地方不需要边界检查,从而把边界检查优化掉,用以提高代码的性能。下面例子:

private readonly int[] _array = new int[7];
public int GetBucket() => GetBucket(_array, 42);
private static int GetBucket(int[] buckets, int hashcode) =>
buckets[(uint)hashcode % buckets.Length];

.NET7 它的 ASM 如下:

; Tests.GetBucket()
       sub       rsp,28
       mov       rcx,[rcx+8]
       mov       eax,2A
       mov       edx,[rcx+8]
       mov       r8d,edx
       xor       edx,edx
       idiv      r8
       cmp       rdx,r8
       jae       short M00_L00
       mov       eax,[rcx+rdx*4+10]
       add       rsp,28
       ret
M00_L00:
       call      CORINFO_HELP_RNGCHKFAIL
       int       3

它依然进行了边界检查,然 .NET8JIT 能自动识别到 (uint)hashcode%buckets.Length 这个索引不可能超过数组的长度也就是 buckets.Length。所以 .NET8 可以省略掉边界检查,如下 .NET8 ASM

; Tests.GetBucket()
       mov       rcx,[rcx+8]
       mov       eax,2A
       mov       r8d,[rcx+8]
       xor       edx,edx
       div       r8
       mov       eax,[rcx+rdx*4+10]
       ret

再看下另外一个例子:

public class Tests
{
   
    private readonly string _s = "\"Hello, World!\"";

    public bool IsQuoted() => IsQuoted(_s);

    private static bool IsQuoted(string s) =>
      s.Length >= 2 && s[0] == '"' && s[^1] == '"';
}

IsQuoted 检查字符串是否至少有两个字符,并且字符串开头和结尾均以引号结束,s[^1] 表示 s[s.Length - 1] 也就是字符串的长度。

.NET7 ASM 如下:

; Tests.IsQuoted(System.String)
       sub       rsp,28
       mov       eax,[rcx+8]
       cmp       eax,2
       jl        short M01_L00
       cmp       word ptr [rcx+0C],22
       jne       short M01_L00
       lea       edx,[rax-1]
       cmp       edx,eax
       jae       short M01_L01
       mov       eax,edx
       cmp       word ptr [rcx+rax*2+0C],22
       sete      al
       movzx     eax,al
       add       rsp,28
       ret
M01_L00:
       xor       eax,eax
       add       rsp,28
       ret
M01_L01:
       call      CORINFO_HELP_RNGCHKFAIL
       int       3

注意看 .NET7 的骚操,它实际上进行了边界检查,但是只检查了一个,因为它只有一个 jae 指令跳转。这是为什么呢?JIT 已经知道不需要对 s[0] 进行边界检查,因为 s.Length >= 2 已经检查过了,只要是小于2 的索引(因为索引是无符号,没有负数)都不需要检查。但是依然对 s[s.Length - 1] 进行了边界检查,所以 .NET7 虽然也是骚操,但是它这个骚操不够彻底。

我们来看下 .NET8 的骚操,.NET8 ASM 如下:

; Tests.IsQuoted(System.String)
       mov       eax,[rcx+8]
       cmp       eax,2
       jl        short M01_L00
       cmp       word ptr [rcx+0C],22
       jne       short M01_L00
       dec       eax
       cmp       word ptr [rcx+rax*2+0C],22
       sete      al
       movzx     eax,al
       ret
M01_L00:
       xor       eax,eax
       ret

完全没有了边界检查,JIT 不仅意识到 s[0] 是安全的,因为检查过了 s.Length >= 2。因为检查过了 s.Length >= 2,还意识到 s.length > s.Length-1 >=1。所以不需要边界检查,全给它优化掉了。

可以看到 .NET8 的性能优化的极致有多厉害,它基本上榨干了 JIT 的引擎,让其进行最大智能化程度的优化。

转载声明:

目录
相关文章
|
4月前
|
存储 开发框架 缓存
.NET8 极致性能优化 VM
VM 是 CLR 的一部分,但是它不包括 GC 和 JIT。它主要的作用是进行类型的识别和 DLL (托管以及非托管)的加载。可以看到 VM 是一个比较重要的部分,.NET8 里面对它也进行了优化,属于核心级的优化。
74 1
|
SQL 存储 .NET
一起谈.NET技术,如何对ASP.NET进行性能优化
  一、SqlDataRead和Dataset的选择   Sqldataread优点:读取数据非常快。如果对返回的数据不需做大量处理的情况下,建议使用SqlDataReader,其性能要比datset好很多。
938 0
|
SQL 程序员 数据库
一起谈.NET技术,DataReader 程序性能优化
  随着 .NET 平台上,LINQ、ORM 框架、Dynamic Data、... 各种数据访问技术不断推陈出新,程序员也一直追着新技术跑,但对底层和代码细节却越来越难以掌控。当项目性能需要调优时,通常也只能对数据库加入更多索引,而多数人已难以对数据访问的代码优化,且手写 SQL 语句的功力似乎也持续退化中。
1470 0
|
SQL 大数据 数据库
一起谈.NET技术,Sql Server性能优化——Partition(管理分区)
  在企业管理器中,虽然有“管理分区”的菜单,里面的内容却可能与你的预想不同,这里并没有提供直接对分区进行操作的方法,所以一些普通的操作,比如“增加分区”、“删除分区”之类的操作就需要通过脚本实现了。   增加分区(Split Partition)   “增加分区”事实上就是将现有的分区分割开,基于此,在SQL Server中应用的是Split操作。
1103 0
|
缓存 异构计算
一起谈.NET技术,Silverlight性能优化纪要
  Silverlight作为微软为富网络应用所做的一个全新的架构,其优秀的表现力让开发者和用户感受到了强烈的冲击,无数的开发者为其着迷,同时微软的广大设计者也在不断地为其完善和充实,同时Silverlight团队也积极的构建Silverlight 5,其初步的版本已在 http://channel9.msdn.com/Series/Silverlight-Firestarter做了演示,从中你可以感受到Silverlight未来之路。
1074 0
|
缓存 异构计算
Silverlig“.NET研究”ht性能优化纪要
  Silverlight作为微软为富网络应用所做的一个全新的架构,其优秀的表现力让开发者和用户感受到了强烈的冲击,无数的开发者为其着迷,同时微软的广大设计者也在不断地为其完善和充实,同时Silverlight团队也积极的构建Silverlight 5,其初步的版本已在 http://channel9.msdn.com/Series/Silverlight-Firestarter做了演示,从中你可以感受到Silverlight未来之路。
1052 0
|
SQL 存储 测试技术
.Net+SQL Server企业应用性能优化笔记—精确查找瓶颈
首先我们需要部署一个测试环境,将Web项目的源代码拷到测试环境Web服务器IIS上,使得可以直接通过IE访问我们的网站。SQL Server环境可以部署在同一台机器上,条件允许的话有专门的数据库测试服务器那当然是更好,没有也无所谓。
976 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
43 0

热门文章

最新文章