您的位置:  首页 > 技术杂谈 > 正文

.NET8极致性能优化Branch

2024-01-09 12:00 https://my.oschina.net/u/5407571/blog/10713025 江湖评谈 次阅读 条评论

前言

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

原文:.NET8极致性能优化Branch ,作者:江湖评谈,欢迎关注。

概述

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

  •  
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如下:

; 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_L03M00_L01:       xor       ebx,ebx       xor       ebp,ebp       jmp       short M00_L00M00_L02:       mov       eax,3M00_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_L05M00_L04:       xor       edx,edx       xor       ebp,ebpM00_L05:       mov       [rsi],rdx       mov       [rsi+8],ebp       mov       rax,rsi       add       rsp,28       pop       rbx       pop       rbp       pop       rsi       pop       rdi       retM00_L06:       call      qword ptr [7FF999FEB498]       int       3; Total bytes of code 136

看下M00_L03

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)进行比较,这里有两个比较

       cmp       eax,ebp  //看这个cmp       jae       short M00_L04       cmp       eax,ebp  //以及这个cmp 它们完全一样 比较

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

M00_L04:       cmp       eax,ebp       jae       short M00_L07       mov       ecx,eax       lea       rdx,[rdi+rcx*2]

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

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

.NET7里面BothGreaterThanOrEqualZero的ASM如下:

​​​​​​​

; Tests.BothGreaterThanOrEqualZero(Int32, Int32)      test      edx,edx      jl        short M00_L00      mov       eax,r8d      not       eax      shr       eax,1F      retM00_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      retM00_L00:      xor       eax,eax      ret; Total bytes of code 16

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

; Tests.BothGreaterThanOrEqualZero(Int32, Int32)       or        edx,r8d       mov       eax,edx       not       eax       shr       eax,1F       ret; Total bytes of code 11

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

展开阅读全文
  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接