经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
.NET8极致性能优化CHRL
来源:cnblogs  作者:江湖评谈  时间:2023/12/1 9:10:17  对本文有异议

前言

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

概述

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

  1. public class Tests
  2. {
  3. private byte[] _array = new byte[8];
  4. private int _index = 4;
  5. public void Get() => Get(_array, _index);
  6. [MethodImpl(MethodImplOptions.NoInlining)]
  7. private static byte Get(byte[] array, int index) => array[index];
  8. }

Get函数.NET7的ASM如下:

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

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

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

.NET7它的ASM如下:

  1. ; Tests.GetBucket()
  2. sub rsp,28
  3. mov rcx,[rcx+8]
  4. mov eax,2A
  5. mov edx,[rcx+8]
  6. mov r8d,edx
  7. xor edx,edx
  8. idiv r8
  9. cmp rdx,r8
  10. jae short M00_L00
  11. mov eax,[rcx+rdx*4+10]
  12. add rsp,28
  13. ret
  14. M00_L00:
  15. call CORINFO_HELP_RNGCHKFAIL
  16. int 3

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

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

再看下另外一个例子:

  1. public class Tests
  2. {
  3. private readonly string _s = "\"Hello, World!\"";
  4. public bool IsQuoted() => IsQuoted(_s);
  5. private static bool IsQuoted(string s) =>
  6. s.Length >= 2 && s[0] == '"' && s[^1] == '"';
  7. }

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

  1. ; Tests.IsQuoted(System.String)
  2. sub rsp,28
  3. mov eax,[rcx+8]
  4. cmp eax,2
  5. jl short M01_L00
  6. cmp word ptr [rcx+0C],22
  7. jne short M01_L00
  8. lea edx,[rax-1]
  9. cmp edx,eax
  10. jae short M01_L01
  11. mov eax,edx
  12. cmp word ptr [rcx+rax*2+0C],22
  13. sete al
  14. movzx eax,al
  15. add rsp,28
  16. ret
  17. M01_L00:
  18. xor eax,eax
  19. add rsp,28
  20. ret
  21. M01_L01:
  22. call CORINFO_HELP_RNGCHKFAIL
  23. int 3

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

  1. ; Tests.IsQuoted(System.String)
  2. mov eax,[rcx+8]
  3. cmp eax,2
  4. jl short M01_L00
  5. cmp word ptr [rcx+0C],22
  6. jne short M01_L00
  7. dec eax
  8. cmp word ptr [rcx+rax*2+0C],22
  9. sete al
  10. movzx eax,al
  11. ret
  12. M01_L00:
  13. xor eax,eax
  14. ret

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

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


点击下加入技术讨论群:

欢迎加入.NET技术交流群

结尾

作者:江湖评谈
欢迎关注公众号:jianghupt,文章首发,以及更多高阶内容分享。
image

原文链接:https://www.cnblogs.com/tangyanzhi1111/p/17864894.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号