前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.NET8极致性能优化Branch

.NET8极致性能优化Branch

作者头像
江湖评谈
发布2023-12-11 18:57:15
1350
发布2023-12-11 18:57:15
举报
文章被收录于专栏:天下风云天下风云

前言

branch(分支)顾名思义:代码通过判断条件形成的执行情况,比如if/else这种判断分支。分支有用的代码是非常有意义的,它意味着是程序不可分割的部分。但是分支无意义的代码同样副作用巨大,比如一个永远不会执行的分支却一直被CLR加载,被JIT编译,被CPU识别,这种开销是巨大的。现代化的硬件,通过流水线技术,预测下一个指令执行的是谁,并且在上一个指令尚未执行完成,下一个指令已经被读取,解码,加载了。这也是一种巨大的开销,为了减少这种开销。目前的技术情况,编译器能做的是努力将分支最小化。本篇来看下。

概述

有一种简单的减少分支影响性能的方法就是完全的删除分支,但是这似乎不可能。比如多个分支通向同一个结果,我们可以通过一种方式找到冗余的分支,全部删掉,只留下一个分支。这是目前的一种优化方案。

代码语言:javascript
复制
public class Tests
{
    private static readonly Random s_rand = new();
    private readonly string _text = "hello world!";

    [Params(1.0, 0.5)]
    public double Probability { get; set; }

    public ReadOnlySpan<char> TrySlice() => SliceOrDefault(_text.AsSpan(), s_rand.NextDouble() < Probability ? 3 : 20);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ReadOnlySpan<char> SliceOrDefault(ReadOnlySpan<char> span, int i)
    {
        if ((uint)i < (uint)span.Length)
        {
            return span.Slice(i);
        }

        return default;
    }
}

TrySlice在.NET7上的ASM如下:

代码语言:javascript
复制
; Tests.TrySlice()
       push      rdi
       push      rsi
       push      rbp
       push      rbx
       sub       rsp,28
       vzeroupper
       mov       rdi,rcx
       mov       rsi,rdx
       mov       rcx,[rdi+8]
       test      rcx,rcx
       je        short M00_L01
       lea       rbx,[rcx+0C]
       mov       ebp,[rcx+8]
M00_L00:
       mov       rcx,1EBBFC01FA0
       mov       rcx,[rcx]
       mov       rcx,[rcx+8]
       mov       rax,[rcx]
       mov       rax,[rax+48]
       call      qword ptr [rax+20]
       vmovsd    xmm1,qword ptr [rdi+10]
       vucomisd  xmm1,xmm0
       ja        short M00_L02
       mov       eax,14
       jmp       short M00_L03
M00_L01:
       xor       ebx,ebx
       xor       ebp,ebp
       jmp       short M00_L00
M00_L02:
       mov       eax,3
M00_L03:
       cmp       eax,ebp
       jae       short M00_L04
       cmp       eax,ebp
       ja        short M00_L06
       mov       edx,eax
       lea       rdx,[rbx+rdx*2]
       sub       ebp,eax
       jmp       short M00_L05
M00_L04:
       xor       edx,edx
       xor       ebp,ebp
M00_L05:
       mov       [rsi],rdx
       mov       [rsi+8],ebp
       mov       rax,rsi
       add       rsp,28
       pop       rbx
       pop       rbp
       pop       rsi
       pop       rdi
       ret
M00_L06:
       call      qword ptr [7FF999FEB498]
       int       3
; Total bytes of code 136

看下M00_L03

代码语言:javascript
复制
M00_L03:
       cmp       eax,ebp
       jae       short M00_L04
       cmp       eax,ebp
       ja        short M00_L06
       mov       edx,eax
       lea       rdx,[rbx+rdx*2]

eax中已经被放置了3或者0x14,把它与dbp(也就是span.length)进行比较,这里有两个比较

代码语言:javascript
复制
       cmp       eax,ebp  //看这个cmp
       jae       short M00_L04
       cmp       eax,ebp  //以及这个cmp 它们完全一样 比较

两个一样的cmp指令,前者cmp是if条件判断,后者cmp是span.Slice内联的机器码。所以这可以进行优化。在.NET8里面一个cmp分支被消除掉了:

代码语言:javascript
复制
M00_L04:
       cmp       eax,ebp
       jae       short M00_L07
       mov       ecx,eax
       lea       rdx,[rdi+rcx*2]

另外一种优化方式是,可以用简单的位操作技术来避免分支。

代码语言:javascript
复制
public class Tests
{

    [Arguments(42, 84)]
    public bool BothGreaterThanOrEqualZero(int i, int j) => i >= 0 && j >= 0;
}

.NET7里面BothGreaterThanOrEqualZero的ASM如下:

代码语言:javascript
复制
; Tests.BothGreaterThanOrEqualZero(Int32, Int32)
      test      edx,edx
      jl        short M00_L00
      mov       eax,r8d
      not       eax
      shr       eax,1F
      ret
M00_L00:
      xor       eax,eax
      ret
; Total bytes of code 16
; Tests.BothGreaterThanOrEqualZero(Int32, Int32)
      test      edx,edx
      jl        short M00_L00
      mov       eax,r8d
      not       eax
      shr       eax,1F
      ret
M00_L00:
      xor       eax,eax
      ret
; Total bytes of code 16

以上代码的&&符号可以用位操作符号来替代,可以将他们重写为等效的(i | j) >= 0。现在.NET8如下:

代码语言:javascript
复制
; Tests.BothGreaterThanOrEqualZero(Int32, Int32)
       or        edx,r8d
       mov       eax,edx
       not       eax
       shr       eax,1F
       ret
; Total bytes of code 11

如上所述,分支完全给避免掉了,不用判断,用位操作替代。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-12-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 江湖评谈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
    • 另外一种优化方式是,可以用简单的位操作技术来避免分支。
      • .NET7里面BothGreaterThanOrEqualZero的ASM如下:
        • 以上代码的&&符号可以用位操作符号来替代,可以将他们重写为等效的(i | j) >= 0。现在.NET8如下:
          • 如上所述,分支完全给避免掉了,不用判断,用位操作替代。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档