.NET9 PreView6 RyuJIT代码布局改进(HIR)

2024-07-15 12:27:59 浏览数 (1)

前言

7月9日微软发布了.NET9 PreView6,本篇看下这个版本的CLR(JIT)第一个改进:代码布局改进。

问题

代码的布局改进,是在RyuJIT的HIR(Hight IR)部分,它实际上是对BB(Basick Block)块的顺序改进。

微软早在.NET6就引入了动态PGO的实验,后续的7,8等版本也进行了BB块的顺序改进,但是BB块的顺序如同顽疾始终受限于代码流程图的的控制,这点一直没变。为了改变这个现状,在过去的几个月围绕重构了 RyuJIT 的流程图数据结构,以消除围绕区块排序的各种限制。

非官方解决

一般来说,代码的流水线指令按照顺序执行。比如执行完前一行代码,后一行继续执行。这样会产生两个概念前任(preds)和继任(succs)。但是有的代码,比如if分支这种类型的代码,它的后一行代码不一定是它需要执行的代码,通过if判断跳转,可能跳转到很多行之后去执行。而优化的地点就在这里。

看一个例子(注意这个例子取自官网,但是多了一行代码:Console.ReadLine()所以分析过程会不太一样。

代码语言:javascript复制
var random = new Random();
int number = random.Next(100);

if (number < 99)
{
   Console.WriteLine("We're in BB02");
}
else
{
   Console.WriteLine("We're in BB03");
}
Console.WriteLine("We're in BB04 -- the successor of BB02 and BB03");
Console.ReadLine();

.NET9 RyuJIT HIR会把以上代码生成五个BB块。C#伪代码如下:

代码语言:javascript复制
// bb01 JIT植入的调试代码:CORINFO_HELP_DBG_IS_JUST_MY_CODE
// bb02
var random = new Random();
int number = random.Next(100);
// bb03
if (number < 99)
{
   Console.WriteLine("We're in BB02");
}
// bb04
else
{
   Console.WriteLine("We're in BB03");
}
// bb05
Console.WriteLine("We're in BB04 -- the successor of BB02 and BB03");
Console.ReadLine();

BB01是JIT植入的调试代码,它的preds和succs如下,这里可以忽略。

代码语言:javascript复制
------------ BB01 [0000] [???..???) -> BB02(1) (always), preds={} succs={BB02}

BB02即是初始化随机数,可以看到它有两个分支。

代码语言:javascript复制
------------ BB02 [0001] [000..019) -> BB04(0.5),BB03(0.5) (cond), preds={BB01} succs={BB03,BB04}

‍BB03

代码语言:javascript复制
------------ BB03 [0002] [019..028) -> BB05(1) (always), preds={BB02} succs={BB05}

BB04

代码语言:javascript复制
------------ BB04 [0003] [028..035) -> BB05(1) (always), preds={BB02} succs={BB05}

BB05

代码语言:javascript复制
------------ BB05 [0004] [035..047) (return), preds={BB03,BB04} succs={}

没有优化的代码的流水线执行路径如下:

代码语言:javascript复制
BB01->BB02->BB03-BB04->BB05

但加入了if分支,则变成了如下,其中变化的BB03/4是if判断里面的内容

代码语言:javascript复制
BB01->BB02->BB03->BB05
BB01->BB02->BB04->BB05

优化点就在这里,JIT会对执行的热点(执行次数较多的代码块)进行排序,假如BB03的热点大于BB04,则可以如下流水执行。注意它这里BB04移动到BB05后面去了,是因为4根本没有执行,所以它的热点在某时刻基本为零。

代码语言:javascript复制
BB01->BB02->BB03->BB05->-BB04

反之呢?因为是随机数,如果BB04的热度超过了BB03,则可如下动态优化

代码语言:javascript复制
BB01->BB02->BB04->BB05->-BB03

BB块

看下热点BB块3/4的实际情况

1.BB03

代码语言:javascript复制
------------ BB03 [0002] [019..028) -> BB05(1) (always), preds={BB02} succs={BB05}

***** BB03 [0002]
STMT00018 ( 0x019[E-] ... 0x019 )
               [000036] ----- -----                         *  NO_OP     void

***** BB03 [0002]
STMT00019 ( 0x01A[E-] ... 0x024 )
               [000038] --CXG -----                         *  CALL      void   System.Console:WriteLine(System.String)
               [000058] H---- ----- arg0 in rcx             --*  CNS_INT(h) ref     'We're in BB02'

***** BB03 [0002]
STMT00020 ( 0x024[E-] ... ??? )
               [000039] ----- -----                         *  NO_OP     void

***** BB03 [0002]
STMT00021 ( 0x025[E-] ... 0x025 )
               [000040] ----- -----                         *  NO_OP     void

***** BB03 [0002]
STMT00022 ( 0x026[E-] ... 0x026 )
               [000041] ----- -----                         *  NOP       void

2.BB04

代码语言:javascript复制
------------ BB04 [0003] [028..035) -> BB05(1) (always), preds={BB02} succs={BB05}

***** BB04 [0003]
STMT00009 ( 0x028[E-] ... 0x028 )
               [000023] ----- -----                         *  NO_OP     void

***** BB04 [0003]
STMT00010 ( 0x029[E-] ... 0x033 )
               [000025] --CXG -----                         *  CALL      void   System.Console:WriteLine(System.String)
               [000059] H---- ----- arg0 in rcx             --*  CNS_INT(h) ref     'We're in BB03'

***** BB04 [0003]
STMT00011 ( 0x033[E-] ... ??? )
               [000026] ----- -----                         *  NO_OP     void

***** BB04 [0003]
STMT00012 ( 0x034[E-] ... 0x034 )
               [000027] ----- -----                         *  NO_OP     void

结尾

.NET9 PreView6的这次优化,实际上是对一些优化方方面的硬骨头进行了敲碎,然后重新组织,配合PGO则是性能的另一大提升。当然它的优化远不止于此,其它的诸如,ARM64 代码生成,代码布局,循环优化,减少地址暴露,AVX10v1 支持,硬件内部代码生成,用于浮点和 SIMD 运算的恒定折叠等,后续继续关注下。

0 人点赞