回复1获取开发者路线图 学习分享丨作者郑子铭 这是DotNetNB公众号的第210篇原创文章 原文StephenToub 翻译郑子铭循环提升和克隆(LoopHoistingandCloning) 我们之前看到PGO是如何与循环提升和克隆互动的,这些优化也有其他改进。 从历史上看,JIT对提升的支持仅限于将一个不变量提升到一个层级。 考虑一下这个例子:〔Benchmark〕 spanstylecolor:rgb(0,0,255);publicspanspanstylecolor:rgb(0,0,255);voidspanspanstylecolor:rgb(163,21,21);Computespan() { spanstylecolor:rgb(0,0,255);forspan(spanstylecolor:rgb(163,21,21);intspanspanstylecolor:rgb(0,128,0);thousandsspanspanstylecolor:rgb(171,86,86);spanspanstylecolor:rgb(136,0,0);0span;thousandsspanstylecolor:rgb(136,0,0);10span;thousands) { spanstylecolor:rgb(0,0,255);forspan(spanstylecolor:rgb(163,21,21);intspanspanstylecolor:rgb(0,128,0);hundredsspanspanstylecolor:rgb(171,86,86);spanspanstylecolor:rgb(136,0,0);0span;hundredsspanstylecolor:rgb(136,0,0);10span;hundreds) { spanstylecolor:rgb(0,0,255);forspan(spanstylecolor:rgb(163,21,21);intspanspanstylecolor:rgb(0,128,0);tensspanspanstylecolor:rgb(171,86,86);spanspanstylecolor:rgb(136,0,0);0span;tensspanstylecolor:rgb(136,0,0);10span;tens) { spanstylecolor:rgb(0,0,255);forspan(spanstylecolor:rgb(163,21,21);intspanspanstylecolor:rgb(0,128,0);onesspanspanstylecolor:rgb(171,86,86);spanspanstylecolor:rgb(136,0,0);0span;onesspanstylecolor:rgb(136,0,0);10span;ones) { spanstylecolor:rgb(163,21,21);intspanspanstylecolor:rgb(0,128,0);nspanspanstylecolor:rgb(171,86,86);spanComputeNumber(thousands,hundreds,tens,ones); Process(n); } } } } } spanstylecolor:rgb(0,0,255);staticspanspanstylecolor:rgb(163,21,21);intspanspanstylecolor:rgb(163,21,21);ComputeNumberspan(spanstylecolor:rgb(163,21,21);intspanthousands,spanstylecolor:rgb(163,21,21);intspanhundreds,spanstylecolor:rgb(163,21,21);intspantens,spanstylecolor:rgb(163,21,21);intspanones) (thousandsspanstylecolor:rgb(136,0,0);1000span) (hundredsspanstylecolor:rgb(136,0,0);100span) (tensspanstylecolor:rgb(136,0,0);10span) ones; 〔MethodImpl(MethodImplOptions。NoInlining)〕 spanstylecolor:rgb(0,0,255);staticspanspanstylecolor:rgb(0,0,255);voidspanspanstylecolor:rgb(163,21,21);Processspan(spanstylecolor:rgb(163,21,21);intspann){} 乍一看,你可能会说:有什么可提升的,n的计算需要所有的循环输入,而所有的计算都在ComputeNumber中。但从编译器的角度来看,ComputeNumber函数是可内联的,因此在逻辑上可以成为其调用者的一部分,n的计算实际上被分成了多块,每块都可以被提升到不同的层级,例如,十的计算可以提升出一层,百的提升出两层,千的提升出三层。下面是〔DisassemblyDiagnoser〕对。NET6的输出。spanstylecolor:rgb(0,128,0);;Program。Compute()span spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);r14span spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);rdispan spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);rsispan spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);rbpspan spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);rbxspan spanstylecolor:rgb(0,0,255);subspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);20span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);esispan,spanstylecolor:rgb(0,0,255);esispan spanstylecolor:rgb(0,176,232);M00L00:span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);edispan,spanstylecolor:rgb(0,0,255);edispan spanstylecolor:rgb(0,176,232);M00L01:span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);ebxspan,spanstylecolor:rgb(0,0,255);ebxspan spanstylecolor:rgb(0,176,232);M00L02:span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);ebpspan,spanstylecolor:rgb(0,0,255);ebpspan spanstylecolor:rgb(0,0,255);imulspanspanstylecolor:rgb(0,0,255);ecxspan,spanstylecolor:rgb(0,0,255);esispan,3E8 spanstylecolor:rgb(0,0,255);imulspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(0,0,255);edispan,spanstylecolor:rgb(136,0,0);64span spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);ecxspan,spanstylecolor:rgb(0,0,255);eaxspan spanstylecolor:rgb(0,0,255);leaspanspanstylecolor:rgb(0,0,255);eaxspan,〔spanstylecolor:rgb(0,0,255);rbxspanspanstylecolor:rgb(0,0,255);rbxspanspanstylecolor:rgb(136,0,0);4span〕 spanstylecolor:rgb(0,0,255);leaspanspanstylecolor:rgb(0,0,255);r14dspan,〔spanstylecolor:rgb(0,0,255);rcxspanspanstylecolor:rgb(0,0,255);raxspanspanstylecolor:rgb(136,0,0);2span〕 spanstylecolor:rgb(0,176,232);M00L03:span spanstylecolor:rgb(0,0,255);leaspanspanstylecolor:rgb(0,0,255);ecxspan,〔spanstylecolor:rgb(0,0,255);r14spanspanstylecolor:rgb(0,0,255);rbpspan〕 spanstylecolor:rgb(0,0,255);callspanProgramspanstylecolor:rgb(136,0,0);。spanProcess(Int32) spanstylecolor:rgb(0,0,255);incspanspanstylecolor:rgb(0,0,255);ebpspan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);ebpspan,0A spanstylecolor:rgb(0,0,255);jlspanshortM00L03 spanstylecolor:rgb(0,0,255);incspanspanstylecolor:rgb(0,0,255);ebxspan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);ebxspan,0A spanstylecolor:rgb(0,0,255);jlspanshortM00L02 spanstylecolor:rgb(0,0,255);incspanspanstylecolor:rgb(0,0,255);edispan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);edispan,0A spanstylecolor:rgb(0,0,255);jlspanshortM00L01 spanstylecolor:rgb(0,0,255);incspanspanstylecolor:rgb(0,0,255);esispan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);esispan,0A spanstylecolor:rgb(0,0,255);jlspanshortM00L00 spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);20span spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);rbxspan spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);rbpspan spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);rsispan spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);rdispan spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);r14span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode84span 我们可以看到,这里发生了一些提升。毕竟,最里面的循环(标记为M00L03)只有五条指令:增加ebp(这时是1的计数器值),如果它仍然小于0xA(10),就跳回到M00L03,把r14中的任何数字加到1上。很好,所以我们已经把所有不必要的计算从内循环中取出来了,只剩下把1的位置加到其余的数字中。让我们再往外走一级。M00L02是十位数循环的标签。我们在这里看到了什么?有问题。两条指令imulecx,esi,3E8和imuleax,edi,64正在进行千位数1000和百位数100的操作,突出表明这些本来可以进一步提升的操作被卡在了最下层的循环中。现在,这是我们在。NET7中得到的结果,在dotnetruntime68061中,这种情况得到了改善:spanstylecolor:rgb(0,128,0);;Program。Compute()span spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);r15span spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);r14span spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);r12span spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);rdispan spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);rsispan spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);rbpspan spanstylecolor:rgb(0,0,255);pushspanspanstylecolor:rgb(0,0,255);rbxspan spanstylecolor:rgb(0,0,255);subspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);20span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);esispan,spanstylecolor:rgb(0,0,255);esispan spanstylecolor:rgb(0,176,232);M00L00:span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);edispan,spanstylecolor:rgb(0,0,255);edispan spanstylecolor:rgb(0,0,255);imulspanspanstylecolor:rgb(0,0,255);ebxspan,spanstylecolor:rgb(0,0,255);esispan,3E8 spanstylecolor:rgb(0,176,232);M00L01:span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);ebpspan,spanstylecolor:rgb(0,0,255);ebpspan spanstylecolor:rgb(0,0,255);imulspanspanstylecolor:rgb(0,0,255);r14dspan,spanstylecolor:rgb(0,0,255);edispan,spanstylecolor:rgb(136,0,0);64span spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);r14dspan,spanstylecolor:rgb(0,0,255);ebxspan spanstylecolor:rgb(0,176,232);M00L02:span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);r15dspan,spanstylecolor:rgb(0,0,255);r15dspan spanstylecolor:rgb(0,0,255);leaspanspanstylecolor:rgb(0,0,255);ecxspan,〔spanstylecolor:rgb(0,0,255);rbpspanspanstylecolor:rgb(0,0,255);rbpspanspanstylecolor:rgb(136,0,0);4span〕 spanstylecolor:rgb(0,0,255);leaspanspanstylecolor:rgb(0,0,255);r12dspan,〔spanstylecolor:rgb(0,0,255);r14spanspanstylecolor:rgb(0,0,255);rcxspanspanstylecolor:rgb(136,0,0);2span〕 spanstylecolor:rgb(0,176,232);M00L03:span spanstylecolor:rgb(0,0,255);leaspanspanstylecolor:rgb(0,0,255);ecxspan,〔spanstylecolor:rgb(0,0,255);r12spanspanstylecolor:rgb(0,0,255);r15span〕 spanstylecolor:rgb(0,0,255);callspanspanstylecolor:rgb(0,0,255);qwordspanspanstylecolor:rgb(0,0,255);ptrspan〔Programspanstylecolor:rgb(136,0,0);。spanProcess(Int32)〕 spanstylecolor:rgb(0,0,255);incspanspanstylecolor:rgb(0,0,255);r15dspan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);r15dspan,0A spanstylecolor:rgb(0,0,255);jlspanshortM00L03 spanstylecolor:rgb(0,0,255);incspanspanstylecolor:rgb(0,0,255);ebpspan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);ebpspan,0A spanstylecolor:rgb(0,0,255);jlspanshortM00L02 spanstylecolor:rgb(0,0,255);incspanspanstylecolor:rgb(0,0,255);edispan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);edispan,0A spanstylecolor:rgb(0,0,255);jlspanshortM00L01 spanstylecolor:rgb(0,0,255);incspanspanstylecolor:rgb(0,0,255);esispan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);esispan,0A spanstylecolor:rgb(0,0,255);jlspanshortM00L00 spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);20span spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);rbxspan spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);rbpspan spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);rsispan spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);rdispan spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);r12span spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);r14span spanstylecolor:rgb(0,0,255);popspanspanstylecolor:rgb(0,0,255);r15span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode99span 现在注意一下这些imul指令的位置。有四个标签,每个标签对应一个循环,我们可以看到最外层的循环有imulebx,esi,3E8(用于千位计算),下一个循环有imulr14d,edi,64(用于百位计算),突出表明这些计算被提升到了适当的层级(十位和一位计算仍然在正确的位置)。 在克隆方面有了更多的改进。以前,循环克隆只适用于从低值到高值的1次迭代循环。有了dotnetruntime60148,与上值的比较可以是,而不仅仅是。有了dotnetruntime67930,向下迭代的循环也可以被克隆,增量和减量大于1的循环也是如此。spanstylecolor:rgb(0,0,255);privatespanspanstylecolor:rgb(0,0,255);intspan〔〕valuesEnumerable。Range(spanstylecolor:rgb(136,0,0);0span,spanstylecolor:rgb(136,0,0);1000span)。ToArray(); 〔spanstylecolor:rgb(43,145,175);Benchmarkspan〕 〔spanstylecolor:rgb(43,145,175);Arguments(0,0,1000)span〕 spanstylecolor:rgb(0,0,255);publicspanspanstylecolor:rgb(0,0,255);intspanspanstylecolor:rgb(163,21,21);LastIndexOfspan(spanstylecolor:rgb(0,0,255);intspanarg,spanstylecolor:rgb(0,0,255);intspanoffset,spanstylecolor:rgb(0,0,255);intspancount) { spanstylecolor:rgb(0,0,255);intspan〔〕valuesvalues; spanstylecolor:rgb(0,0,255);forspan(spanstylecolor:rgb(0,0,255);intspanioffsetcountspanstylecolor:rgb(136,0,0);1span;ioffset;i) spanstylecolor:rgb(0,0,255);ifspan(values〔i〕arg) spanstylecolor:rgb(0,0,255);returnspani; spanstylecolor:rgb(0,0,255);returnspanspanstylecolor:rgb(136,0,0);0span; } 如果没有循环克隆,JIT不能假设offset到offsetcount都在范围内,因此对数组的每个访问都需要进行边界检查。有了循环克隆,JIT可以生成一个没有边界检查的循环版本,并且只在它知道所有的访问都是有效的时候使用。这正是现在。NET7中发生的事情。下面是我们在。NET6中得到的情况。spanstylecolor:rgb(0,128,0);;Program。LastIndexOf(Int32,Int32,Int32)span spanstylecolor:rgb(0,0,255);subspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);rcxspan,〔spanstylecolor:rgb(0,0,255);rcxspanspanstylecolor:rgb(136,0,0);8span〕 spanstylecolor:rgb(0,0,255);leaspanspanstylecolor:rgb(0,0,255);eaxspan,〔spanstylecolor:rgb(0,0,255);r8spanspanstylecolor:rgb(0,0,255);r9span0FFFF〕 spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(0,0,255);r8dspan spanstylecolor:rgb(0,0,255);jlspanshortM00L01 spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);r9dspan,〔spanstylecolor:rgb(0,0,255);rcxspanspanstylecolor:rgb(136,0,0);8span〕 spanstylecolor:rgb(0,0,255);nopspanspanstylecolor:rgb(0,0,255);wordspanspanstylecolor:rgb(0,0,255);ptrspan〔spanstylecolor:rgb(0,0,255);raxspanspanstylecolor:rgb(0,0,255);raxspan〕 spanstylecolor:rgb(0,176,232);M00L00:span spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(0,0,255);r9dspan spanstylecolor:rgb(0,0,255);jaespanshortM00L03 spanstylecolor:rgb(0,0,255);movsxdspanspanstylecolor:rgb(0,0,255);r10span,spanstylecolor:rgb(0,0,255);eaxspan spanstylecolor:rgb(0,0,255);cmpspan〔spanstylecolor:rgb(0,0,255);rcxspanspanstylecolor:rgb(0,0,255);r10spanspanstylecolor:rgb(136,0,0);4spanspanstylecolor:rgb(136,0,0);10span〕,spanstylecolor:rgb(0,0,255);edxspan spanstylecolor:rgb(0,0,255);jespanshortM00L02 spanstylecolor:rgb(0,0,255);decspanspanstylecolor:rgb(0,0,255);eaxspan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(0,0,255);r8dspan spanstylecolor:rgb(0,0,255);jgespanshortM00L00 spanstylecolor:rgb(0,176,232);M00L01:span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(0,0,255);eaxspan spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,176,232);M00L02:span spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,176,232);M00L03:span spanstylecolor:rgb(0,0,255);callspanCORINFOHELPRNGCHKFAIL spanstylecolor:rgb(0,0,255);intspanspanstylecolor:rgb(136,0,0);3span spanstylecolor:rgb(0,128,0);;Totalbytesofcode72span 注意在核心循环中,在标签M00L00处,有一个边界检查(cmpeax,r9dandjaeshortM00L03,它跳到一个调用CORINFOHELPRNGCHKFAIL)。而这里是我们在。NET7中得到的结果。spanstylecolor:rgb(0,128,0);;Program。LastIndexOf(Int32,Int32,Int32)span spanstylecolor:rgb(0,0,255);subspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);raxspan,〔spanstylecolor:rgb(0,0,255);rcxspanspanstylecolor:rgb(136,0,0);8span〕 spanstylecolor:rgb(0,0,255);leaspanspanstylecolor:rgb(0,0,255);ecxspan,〔spanstylecolor:rgb(0,0,255);r8spanspanstylecolor:rgb(0,0,255);r9span0FFFF〕 spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);ecxspan,spanstylecolor:rgb(0,0,255);r8dspan spanstylecolor:rgb(0,0,255);jlspanshortM00L02 spanstylecolor:rgb(0,0,255);testspanspanstylecolor:rgb(0,0,255);raxspan,spanstylecolor:rgb(0,0,255);raxspan spanstylecolor:rgb(0,0,255);jespanshortM00L01 spanstylecolor:rgb(0,0,255);testspanspanstylecolor:rgb(0,0,255);ecxspan,spanstylecolor:rgb(0,0,255);ecxspan spanstylecolor:rgb(0,0,255);jlspanshortM00L01 spanstylecolor:rgb(0,0,255);testspanspanstylecolor:rgb(0,0,255);r8dspan,spanstylecolor:rgb(0,0,255);r8dspan spanstylecolor:rgb(0,0,255);jlspanshortM00L01 spanstylecolor:rgb(0,0,255);cmpspan〔spanstylecolor:rgb(0,0,255);raxspanspanstylecolor:rgb(136,0,0);8span〕,spanstylecolor:rgb(0,0,255);ecxspan spanstylecolor:rgb(0,0,255);jlespanshortM00L01 spanstylecolor:rgb(0,176,232);M00L00:span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);r9dspan,spanstylecolor:rgb(0,0,255);ecxspan spanstylecolor:rgb(0,0,255);cmpspan〔spanstylecolor:rgb(0,0,255);raxspanspanstylecolor:rgb(0,0,255);r9spanspanstylecolor:rgb(136,0,0);4spanspanstylecolor:rgb(136,0,0);10span〕,spanstylecolor:rgb(0,0,255);edxspan spanstylecolor:rgb(0,0,255);jespanshortM00L03 spanstylecolor:rgb(0,0,255);decspanspanstylecolor:rgb(0,0,255);ecxspan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);ecxspan,spanstylecolor:rgb(0,0,255);r8dspan spanstylecolor:rgb(0,0,255);jgespanshortM00L00 spanstylecolor:rgb(0,0,255);jmpspanshortM00L02 spanstylecolor:rgb(0,176,232);M00L01:span spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);ecxspan,〔spanstylecolor:rgb(0,0,255);raxspanspanstylecolor:rgb(136,0,0);8span〕 spanstylecolor:rgb(0,0,255);jaespanshortM00L04 spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);r9dspan,spanstylecolor:rgb(0,0,255);ecxspan spanstylecolor:rgb(0,0,255);cmpspan〔spanstylecolor:rgb(0,0,255);raxspanspanstylecolor:rgb(0,0,255);r9spanspanstylecolor:rgb(136,0,0);4spanspanstylecolor:rgb(136,0,0);10span〕,spanstylecolor:rgb(0,0,255);edxspan spanstylecolor:rgb(0,0,255);jespanshortM00L03 spanstylecolor:rgb(0,0,255);decspanspanstylecolor:rgb(0,0,255);ecxspan spanstylecolor:rgb(0,0,255);cmpspanspanstylecolor:rgb(0,0,255);ecxspan,spanstylecolor:rgb(0,0,255);r8dspan spanstylecolor:rgb(0,0,255);jgespanshortM00L01 spanstylecolor:rgb(0,176,232);M00L02:span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(0,0,255);eaxspan spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,176,232);M00L03:span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(0,0,255);ecxspan spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,176,232);M00L04:span spanstylecolor:rgb(0,0,255);callspanCORINFOHELPRNGCHKFAIL spanstylecolor:rgb(0,0,255);intspanspanstylecolor:rgb(136,0,0);3span spanstylecolor:rgb(0,128,0);;Totalbytesofcode98span 注意到代码大小是如何变大的,以及现在有两个循环的变化:一个在M00L00,一个在M00L01。第二个,M00L01,有一个分支到那个相同的调用CORINFOHELPRNGCHKFAIL,但第一个没有,因为那个循环最终只会在证明偏移量、计数和values。Length是这样的,即索引将总是在界内之后被使用。 dotnetruntime59886使JIT能够选择不同的形式来发出选择快速或慢速循环路径的条件,例如,是否发出所有的条件,与它们一起,然后分支(if(!(cond1cond2))gotoslowPath),或者是否单独发出每个条件(if(!cond1)gotoslowPath;if(!cond2)gotoslowPath)。dotnetruntime66257使循环变量被初始化为更多种类的表达式时,循环克隆得以启动(例如,for(intfromindexlastIndexlengthToClear;。。。))。dotnetruntime70232增加了JIT克隆具有更广泛操作的主体的循环的意愿。折叠、传播和替换(Folding,propagation,andsubstitution) 常量折叠是一种优化,编译器在编译时计算只涉及常量的表达式的值,而不是在运行时生成代码来计算该值。在。NET中有多个级别的常量折叠,有些常量折叠由C编译器执行,有些常量折叠由JIT编译器执行。例如,给定C代码。〔spanstylecolor:rgb(43,145,175);Benchmarkspan〕 spanstylecolor:rgb(0,0,255);publicspanspanstylecolor:rgb(0,0,255);intspanspanstylecolor:rgb(163,21,21);Aspan()spanstylecolor:rgb(136,0,0);3span(spanstylecolor:rgb(136,0,0);4spanspanstylecolor:rgb(136,0,0);5span); 〔spanstylecolor:rgb(43,145,175);Benchmarkspan〕 spanstylecolor:rgb(0,0,255);publicspanspanstylecolor:rgb(0,0,255);intspanspanstylecolor:rgb(163,21,21);Bspan()A()spanstylecolor:rgb(136,0,0);2span; C编译器将为这些方法生成IL,如下所示。spanstylecolor:rgb(43,145,175);。methodspanspanstylecolor:rgb(43,145,175);publicspanhidebysiginstanceint32A()cilmanaged { spanstylecolor:rgb(43,145,175);。maxstackspanspanstylecolor:rgb(136,0,0);8span spanstylecolor:rgb(0,176,232);IL0000:spanldcspanstylecolor:rgb(136,0,0);。spani4spanstylecolor:rgb(136,0,0);。spansspanstylecolor:rgb(136,0,0);23span spanstylecolor:rgb(0,176,232);IL0002:spanspanstylecolor:rgb(0,0,255);retspan } spanstylecolor:rgb(43,145,175); 。methodspanspanstylecolor:rgb(43,145,175);publicspanhidebysiginstanceint32B()cilmanaged { spanstylecolor:rgb(43,145,175);。maxstackspanspanstylecolor:rgb(136,0,0);8span spanstylecolor:rgb(0,176,232);IL0000:spanldargspanstylecolor:rgb(136,0,0);。0span spanstylecolor:rgb(0,176,232);IL0001:spanspanstylecolor:rgb(0,0,255);callspaninstanceint32Program::A() spanstylecolor:rgb(0,176,232);IL0006:spanldcspanstylecolor:rgb(136,0,0);。spani4spanstylecolor:rgb(136,0,0);。2span spanstylecolor:rgb(0,176,232);IL0007:spanspanstylecolor:rgb(0,0,255);mulspan spanstylecolor:rgb(0,176,232);IL0008:spanspanstylecolor:rgb(0,0,255);retspan } 你可以看到,C编译器已经计算出了3(45)的值,因为方法A的IL只是包含了相当于返回23;的内容。然而,方法B包含了相当于returnA()2;的内容,突出表明C编译器所进行的常量折叠只是在方法内部进行的。现在是JIT生成的内容。spanstylecolor:rgb(0,128,0);;Program。A()span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(136,0,0);17span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode6span spanstylecolor:rgb(0,128,0);;Program。B()span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);eaxspan,2E spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode6span 方法A的汇编并不特别有趣;它只是返回相同的值23(十六进制0x17)。但方法B更有趣。JIT已经内联了从B到A的调用,将A的内容暴露给B,这样JIT就有效地将B的主体视为等同于返回232;。在这一点上,JIT可以做自己的常量折叠,它将B的主体转化为简单的返回46(十六进制0x2e)。常量传播与常量折叠有着错综复杂的联系,本质上就是你可以将一个常量值(通常是通过常量折叠计算出来的)替换到进一步的表达式中,这时它们也可以被折叠。 JIT长期以来一直在进行恒定折叠,但它在。NET7中得到了进一步改善。常量折叠的改进方式之一是暴露出更多需要折叠的值,这往往意味着更多的内联。dotnetruntime55745帮助inliner理解像M(constantconstant)这样的方法调用(注意到这些常量可能是其他方法调用的结果)本身就是在向M传递常量,而常量被传递到方法调用中是在提示inliner应该考虑更积极地进行内联,因为将该常量暴露给被调用者的主体有可能大大减少实现被调用者所需的代码量。JIT之前可能已经内联了这样的方法,但是当涉及到内联时,JIT是关于启发式方法和产生足够的证据来证明值得内联的东西;这有助于这些证据。例如,这种模式出现在TimeSpan的各种FromXx方法中。例如,TimeSpan。FromSeconds被实现为。spanstylecolor:rgb(0,0,255);publicspanspanstylecolor:rgb(163,21,21);staticspanTimeSpanspanstylecolor:rgb(163,21,21);FromSecondsspan(spanstylecolor:rgb(163,21,21);doublespanvalue)spanstylecolor:rgb(0,0,255);Intervalspan(value,TicksPerSecond);spanstylecolor:rgb(0,128,0);TicksPerSecondisaconstantspan 并且,为了这个例子的目的,避开了参数验证,Interval是。spanstylecolor:rgb(0,0,255);privatespanspanstylecolor:rgb(163,21,21);staticspanTimeSpanspanstylecolor:rgb(163,21,21);Intervalspan(spanstylecolor:rgb(163,21,21);doublespanvalue,spanstylecolor:rgb(163,21,21);doublespanscale)spanstylecolor:rgb(0,0,255);IntervalFromDoubleTicksspan(valuescale); spanstylecolor:rgb(0,0,255);privatespanspanstylecolor:rgb(163,21,21);staticspanTimeSpanspanstylecolor:rgb(163,21,21);IntervalFromDoubleTicksspan(spanstylecolor:rgb(163,21,21);doublespanticks)ticksspanstylecolor:rgb(163,21,21);longspan。MaxValue?TimeSpan。MaxValue:spanstylecolor:rgb(0,0,255);newspanspanstylecolor:rgb(0,0,255);TimeSpanspan((spanstylecolor:rgb(163,21,21);longspan)ticks); 如果所有的东西都被内联,意味着FromSeconds本质上是。spanstylecolor:rgb(0,0,255);publicspanspanstylecolor:rgb(163,21,21);staticspanTimeSpanspanstylecolor:rgb(163,21,21);FromSecondsspan(spanstylecolor:rgb(163,21,21);doublespanvalue) { spanstylecolor:rgb(163,21,21);doublespanticksvaluespanstylecolor:rgb(136,0,0);10span000000; spanstylecolor:rgb(0,0,255);returnspanticksspanstylecolor:rgb(163,21,21);longspan。MaxValue?TimeSpan。MaxValue:spanstylecolor:rgb(0,0,255);newspanspanstylecolor:rgb(0,0,255);TimeSpanspan((spanstylecolor:rgb(163,21,21);longspan)ticks); } 如果值是一个常数,比方说5,整个事情可以被常数折叠(在tickslong。MaxValue分支上消除了死代码),简单地说。spanstylecolor:rgb(0,0,255);returnspanspanstylecolor:rgb(0,0,255);newspanspanstylecolor:rgb(0,0,255);TimeSpanspan(spanstylecolor:rgb(136,0,0);50span000000); 我就不说。NET6的程序集了,但在。NET7上,用这样的基准来衡量。〔spanstylecolor:rgb(43,145,175);Benchmarkspan〕 spanstylecolor:rgb(0,0,255);publicspanTimeSpanspanstylecolor:rgb(163,21,21);FromSecondsspan()TimeSpan。FromSeconds(spanstylecolor:rgb(136,0,0);5span); 我们现在得到的是简单和干净。spanstylecolor:rgb(0,128,0);;Program。FromSeconds()span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);eaxspan,2FAF080 spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode6span 另一个改进常量折叠的变化包括来自SingleAccretion的dotnetruntime57726,它在一种特殊的情况下解除了常量折叠,这种情况有时表现为对从方法调用返回的结构进行逐字段赋值。作为一个小例子,考虑这个微不足道的属性,它访问了Color。DarkOrange属性,而后者又做了newColor(KnownColor。DarkOrange)。〔spanstylecolor:rgb(43,145,175);Benchmarkspan〕 spanstylecolor:rgb(0,0,255);publicspanColorspanstylecolor:rgb(163,21,21);DarkOrangespan()Color。DarkOrange; 在。NET6中,JIT生成了这个。spanstylecolor:rgb(0,128,0);;Program。DarkOrange()span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(136,0,0);1span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);ecxspan,spanstylecolor:rgb(136,0,0);39span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);r8dspan,spanstylecolor:rgb(0,0,255);r8dspan spanstylecolor:rgb(0,0,255);movspan〔spanstylecolor:rgb(0,0,255);rdxspan〕,spanstylecolor:rgb(0,0,255);r8span spanstylecolor:rgb(0,0,255);movspan〔spanstylecolor:rgb(0,0,255);rdxspanspanstylecolor:rgb(136,0,0);8span〕,spanstylecolor:rgb(0,0,255);r8span spanstylecolor:rgb(0,0,255);movspan〔spanstylecolor:rgb(0,0,255);rdxspanspanstylecolor:rgb(136,0,0);10span〕,spanstylecolor:rgb(0,0,255);cxspan spanstylecolor:rgb(0,0,255);movspan〔spanstylecolor:rgb(0,0,255);rdxspanspanstylecolor:rgb(136,0,0);12span〕,spanstylecolor:rgb(0,0,255);axspan spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);raxspan,spanstylecolor:rgb(0,0,255);rdxspan spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode32span 有趣的是,一些常量(39,是KnownColor。DarkOrange的值,和1,是一个私有的StateKnownColorValid常量)被加载到寄存器中(moveax,1thenmovecx,39),然后又被存储到被返回的颜色结构的相关位置(mov〔rdx12〕,axandmov〔rdx10〕,cx)。在。NET7中,它现在产生了。spanstylecolor:rgb(0,128,0);;Program。DarkOrange()span spanstylecolor:rgb(0,0,255);xorspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(0,0,255);eaxspan spanstylecolor:rgb(0,0,255);movspan〔spanstylecolor:rgb(0,0,255);rdxspan〕,spanstylecolor:rgb(0,0,255);raxspan spanstylecolor:rgb(0,0,255);movspan〔spanstylecolor:rgb(0,0,255);rdxspanspanstylecolor:rgb(136,0,0);8span〕,spanstylecolor:rgb(0,0,255);raxspan spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);wordspanspanstylecolor:rgb(0,0,255);ptrspan〔spanstylecolor:rgb(0,0,255);rdxspanspanstylecolor:rgb(136,0,0);10span〕,spanstylecolor:rgb(136,0,0);39span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);wordspanspanstylecolor:rgb(0,0,255);ptrspan〔spanstylecolor:rgb(0,0,255);rdxspanspanstylecolor:rgb(136,0,0);12span〕,spanstylecolor:rgb(136,0,0);1span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);raxspan,spanstylecolor:rgb(0,0,255);rdxspan spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode25span 直接将这些常量值分配到它们的目标位置(movwordptr〔rdx12〕,1和movwordptr〔rdx10〕,39)。其他有助于常量折叠的变化包括来自SingleAccretion的dotnetruntime58171和来自SingleAccretion的dotnetruntime57605。 然而,一大类改进来自与传播有关的优化,即正向替换。考虑一下这个愚蠢的基准。〔spanstylecolor:rgb(43,145,175);Benchmarkspan〕 spanstylecolor:rgb(0,0,255);publicspanspanstylecolor:rgb(0,0,255);intspanspanstylecolor:rgb(163,21,21);Compute1span()ValueValueValueValueValue; 〔spanstylecolor:rgb(43,145,175);Benchmarkspan〕 spanstylecolor:rgb(0,0,255);publicspanspanstylecolor:rgb(0,0,255);intspanspanstylecolor:rgb(163,21,21);Compute2span()SomethingElse()ValueValueValueValueValue; spanstylecolor:rgb(0,0,255);privatespanspanstylecolor:rgb(0,0,255);staticspanspanstylecolor:rgb(0,0,255);intspanValuespanstylecolor:rgb(136,0,0);16span; 〔spanstylecolor:rgb(43,145,175);MethodImpl(MethodImplOptions。NoInlining)span〕 spanstylecolor:rgb(0,0,255);privatespanspanstylecolor:rgb(0,0,255);staticspanspanstylecolor:rgb(0,0,255);intspanspanstylecolor:rgb(163,21,21);SomethingElsespan()spanstylecolor:rgb(136,0,0);42span; 如果我们看一下在。NET6上为Compute1生成的汇编代码,它看起来和我们希望的一样。我们将Value加了5次,Value被简单地内联,并返回一个常量值16,因此我们希望为Compute1生成的汇编代码实际上只是返回值80(十六进制0x50),这正是发生的情况。spanstylecolor:rgb(0,128,0);;Program。Compute1()span spanstylecolor:rgb(0,0,255);movspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(136,0,0);50span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode6span 但Compute2有点不同。代码的结构是这样的:对SomethingElse的额外调用最终会稍微扰乱JIT的分析,而。NET6最终会得到这样的汇编代码。spanstylecolor:rgb(0,128,0);;Program。Compute2()span spanstylecolor:rgb(0,0,255);subspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);callspanProgramspanstylecolor:rgb(136,0,0);。spanSomethingElse() spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(136,0,0);10span spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(136,0,0);10span spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(136,0,0);10span spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(136,0,0);10span spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(136,0,0);10span spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode29span 我们不是用一个moveax,50来把数值0x50放到返回寄存器中,而是用5个独立的addeax,10来建立同样的0x50(80)的数值。这。。。。。。并不理想。 事实证明,许多JIT的优化都是在解析IL的过程中创建的树状数据结构上进行的。在某些情况下,当它们接触到更多的程序时,优化可以做得更好,换句话说,当它们所操作的树更大,包含更多需要分析的内容时。然而,各种操作可以将这些树分解成更小的、单独的树,比如作为内联的一部分而创建的临时变量,这样做可以抑制这些操作。为了有效地将这些树缝合在一起,我们需要一些东西,这就是前置置换(forwardsubstitution)。你可以把前置置换看成是CSE的逆向操作;与其通过计算一次数值并将其存储到一个临时变量中来寻找重复的表达式并消除它们,不如前置置换来消除这个临时变量并有效地将表达式树移到它的使用位置。显然,如果这样做会否定CSE并导致重复工作的话,你是不想这样做的,但是对于那些只定义一次并使用一次的表达式来说,这种前置传播是很有价值的。dotnetruntime61023添加了一个最初的有限的前置置换版本,然后dotnetruntime63720添加了一个更强大的通用实现。随后,dotnetruntime70587将其扩展到了一些SIMD向量,然后dotnetruntime71161进一步改进了它,使其能够替换到更多的地方(在这种情况下是替换到调用参数)。有了这些,我们愚蠢的基准现在在。NET7上产生如下结果。spanstylecolor:rgb(0,128,0);;Program。Compute2()span spanstylecolor:rgb(0,0,255);subspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);callspanspanstylecolor:rgb(0,0,255);qwordspanspanstylecolor:rgb(0,0,255);ptrspan〔7FFCB8DAF9A8〕 spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);eaxspan,spanstylecolor:rgb(136,0,0);50span spanstylecolor:rgb(0,0,255);addspanspanstylecolor:rgb(0,0,255);rspspan,spanstylecolor:rgb(136,0,0);28span spanstylecolor:rgb(0,0,255);retspan spanstylecolor:rgb(0,128,0);;Totalbytesofcode18span 原文链接 PerformanceImprovementsin。NET7 推荐阅读:【译】。NET7中的性能改进(四) 【译】。NET7中的性能改进(三) 【译】。NET7中的性能改进(二) 【译】。NET7中的性能改进(一) 一款针对EFCore轻量级分表分库、读写分离的开源项目 跟我一起掌握AspNetCore底层技术和构建原理 点击下方卡片关注DotNetNB 一起交流学习 点击上方卡片关注DotNetNB,一起交流学习 请在公众号后台回复【路线图】获取。NET2021开发者路线图回复【原创内容】获取公众号原创内容回复【峰会视频】获取。NETConf开发者大会视频回复【个人简介】获取作者个人简介回复【年终总结】获取作者年终总结回复【加群】加入DotNetNB交流学习群 和我一起,交流学习,分享心得。