.Net8优化技术之常量优化

前言

常量经常是代码里面不可或缺的,常量与一些表达式组合或者自身是一个表达式需要一些计算结果。这些常量如果能被直接计算出来,则可以进行适度的优化。常量优化目前在.Net8里面有Roslyn前端优化,以及JIT后端优化两种。本篇,看下这些优化的方式。

概括

1.常量表达式的优化
C# Source

[Benchmark]
public int A() => 3 + (4 * 5);


[Benchmark]
public int B() => A() * 2;

Roslyn IL

Method A()
 // 代码大小       3 (0x3)
  .maxstack  8
  IL_0000:  ldc.i4.s   23
  IL_0002:  ret


   Method B
   // 代码大小       9 (0x9)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  call       instance int32 Program::A()
  IL_0006:  ldc.i4.2
  IL_0007:  mul
  IL_0008:  ret

在函数A里面可以看到在前端Roslyn里面它就会计算出常量表达式:3 + (4 * 5)的值。

JIT Machine Cdoe

## .NET 8.0.0 (8.0.23.17408), X64 RyuJIT AVX2
```assembly
; Program.A()
       mov       eax,17
       ret
; Total bytes of code 6
```


## .NET 8.0.0 (8.0.23.17408), X64 RyuJIT AVX2
```assembly
; Program.B()
       mov       eax,2E
       ret
; Total bytes of code 6
```

JIT直接返回结果,并没有与函数A()计算结果。可以看到优化的很完美。

2.内联常量优化
C# Code

public static TimeSpan FromSeconds(double value) => Interval(value, TicksPerSecond); // TicksPerSecond is a constant ==10_000_000
   private static TimeSpan Interval(double value, double scale) => IntervalFromDoubleTicks(value * scale);
   private static TimeSpan IntervalFromDoubleTicks(double ticks) => ticks == long.MaxValue ? TimeSpan.MaxValue : new TimeSpan((long)ticks);

把这段代码内联起来,变成了如下所示:

public static TimeSpan FromSeconds(double value)
{
    double ticks = value * 10_000_000;
    return ticks == long.MaxValue ? TimeSpan.MaxValue : new TimeSpan((long)ticks);
}

JIT Machine Code

## .NET 8.0.0 (8.0.23.17408), X64 RyuJIT AVX2
```assembly
; Program.FromSeconds()
       mov       eax,2FAF080
       ret
; Total bytes of code 6
```

它只是返回一个常量,非常不错。没有复杂的分支和复杂的判断计算。以及new的可能巨大的开销。

3.结构体的赋值常量优化
C# Code

[Benchmark]
public Color DarkOrange() => Color.DarkOrange;

.Net 8 JIT Machine Code

## .NET 8.0.0 (8.0.23.17408), X64 RyuJIT AVX2
```assembly
; Program.DarkOrange()
       xor       eax,eax
       mov       [rdx],rax
       mov       [rdx+8],rax
       mov       word ptr [rdx+10],39
       mov       word ptr [rdx+12],1
       mov       rax,rdx
       ret
; Total bytes of code 25
```

39和1这两个常量被返回Color结构体相应的位置。但是在.Net6里面,它多做了一层寄存器传递。

.Net 6 JIT Machine Code

## .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
```assembly
; Program.DarkOrange()
       mov       eax,1
       mov       ecx,39
       xor       r8d,r8d
       mov       [rdx],r8
       mov       [rdx+8],r8
       mov       [rdx+10],cx
       mov       [rdx+12],ax
       mov       rax,rdx
       ret
; Total bytes of code 32
```

先把常量39和1给了寄存器exc,eax。然后在把这两个寄存器赋值给Color结构体相应的内存。这个过程看起来是可以优化的,就是上面的.Net JIT 的Machine Code优化代码。根据结构体的内存来优化掉冗余代码,直接把结果赋值给结构体内存位置。

4.函数与多个常量表达式操作的优化
C# Code

[Benchmark]
    public int Compute1() => Value + Value + Value + Value + Value;
    [Benchmark]
    public int Compute2() => SomethingElse() + Value + Value + Value + Value + Value;
    private static int Value => 16;
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int SomethingElse() => 42;

无论在.Net6或者.Net8函数Compute1都是被优化成常量0x50直接返回,关键点在于Compute2函数的优化。

.Net6 JIT Machine Code for Compute2

## .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
```assembly
; Program.Compute2()
       sub       rsp,28
       call      Program.SomethingElse()
       add       eax,10
       add       eax,10
       add       eax,10
       add       eax,10
       add       eax,10
       add       rsp,28
       ret
; Total bytes of code 29
```

.Net6里面几个Value常量通过寄存器eax相加,这种看似并不是太美观,而且性能上也有问题。

.Net8 JIT Machine Code for compute2

## .NET 8.0.0 (8.0.23.17408), X64 RyuJIT AVX2
```assembly
; Program.Compute2()
       sub       rsp,28
       call      qword ptr [7FFEE5C11180]; Program.SomethingElse()
       add       eax,50
       add       rsp,28
       ret
; Total bytes of code 18

看到.Net8里面几个Value被优化成了0x50,与返回的SomethingElse()值直接相加。达到原有几倍以上的优化程度。

结尾

作者:江湖评谈

参考:

https://devblogs.microsoft.com/dotnet/performance_improvements_in_net_7/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值