在。Net7中性能改进常量折叠
前言
本文是 Performance Improvements in .NET 7 Folding(折叠), propagation(传播), and substitution(替换) 部分的翻译.下面开始正文://原文地址: // https://devblogs.microsoft.com/dotnet/performance_improvements_in_net_7/
常量 折叠是一种优化,编译器在编译时计算只涉及常量的表达式的值,而不是在运行时生成代码来计算值.在.Net中有多个级别的常量折叠,其中一些常量折叠由C#编译器执行,另一些常量折叠由JIT编译器执行.例如给定C#代码:[Benchmark] public int A() => 3 + (4 * 5); [Benchmark] public int B() => A() * 2;
C#编译器将为这些方法生成IL代码,如下所示: .method public hidebysig instance int32 A () cil managed { .maxstack 8 IL_0000: ldc.i4.s 23 //在编译时,由编译器计算值 IL_0002: ret } .method public hidebysig instance int32 B () cil managed { .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance int32 Program::A() //调用方法A,可以看到没有常量折叠和常量传播 IL_0006: ldc.i4.2 IL_0007: mul IL_0008: ret }
您可以看到,C#编译器已经计算出了 3 +(4*5) 的值,因为方法A的IL包含了等价的 return 23 ;但是,方法 B包含等价的 return A()* 2 ; ,强调C#编译器执行的常量折叠只是在方法内部.下面是JIT生成的结果:// Program.A() mov eax,17 //17是十六进制,为十进制的23 ret // Total bytes of code 6 // Program.B() mov eax,2E //2E为十六进制,为十进制的46 ret // Total bytes of code 6
方法A 的汇编代码是不是特别有趣,它只是返回相同的值23(十六进制0x17).但是方法B 更有趣.JIT内联了从 B 到 A 的调用,将 A 的内容暴露给 B ,这样JIT就有效地将 B 的主体看作是等价于 return 23 * 2 ;此时,JIT可以完成自己的常量折叠,并将B的主体转换为简单地返回46(十六进制0x2e).常量传播与常量折叠有着错综复杂的联系,本质上就是可以将常量值(通常是通过常量折叠计算的值)替换为进一步的表达式,此时它们也可以被折叠.
JIT长期以来一直在执行常量折叠,但它在.NET7中得到了进一步改进.常量折叠可以改进的方法之一是公开更多要折叠的值,这意味着更多的内联.dotnet/runtime#55745帮助理解内联, 像M(constant + constant) (注意这些常量可能是其他方法调用的结果)这样的方法调用本身就是将常量传递给M,而传递给方法调用的常量是对内联线的提示,它应该考虑更积极地内联,因为将该常量公开给被调用方的主体可能会显著减少实现被调用方所需的代码量.JIT之前可能已经内联了这样一个方法,但当涉及内联时,JIT都是关于启发式和生成足够的证据来证明内联是值得的;这有助于证明这一点.例如,该模式显示在TimeSpan 上的各种FromXx 方法中.例如TimeSpan.FromSeconds 实现为:// TicksPerSecond is a constant public static TimeSpan FromSeconds(double value) => Interval(value, TicksPerSecond);
并且,为了本例的目的,避免参数验证, Interval 为: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);
如果所有内容都内联,则FromSeconds 本质上是:public static TimeSpan FromSeconds(double value) { double ticks = value * 10_000_000; return ticks == long.MaxValue ? TimeSpan.MaxValue : new TimeSpan((long)ticks); }
如果value 是一个常量,比如5,那么这里就可以被折叠成常量(在ticks == long.MaxValue 分支上消除死代码)简单地:return new TimeSpan(50_000_000);
为此,我将省去.Net 6生成汇编代码,但在.Net 7中,有这样一个基准测试:[Benchmark] public TimeSpan FromSeconds() => TimeSpan.FromSeconds(5);
我们现在得到的是简单明了的汇编代码:// Program.FromSeconds() mov eax,2FAF080 //2FAF080为5*1000*1000 ret // Total bytes of code 6
另一个改进常量折叠的更改包括@SingleAccretion的dotnet/runtime#57726,它在特定的场景中消除了常量折叠,有时在对从方法调用返回的结构进行逐字段赋值时显示.作为一个小例子,考虑这个访问Color.DarkOrange 属性,它会产生new Color(KnownColor.DarkOrange) : [Benchmark] public Color DarkOrange() => Color.DarkOrange;
在.Net 6中,JIT生成如下代码:// 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是KnownColor.DarkOrange 常量值,1是私有StateKnownColorValid 常量值)被加载到寄存器(mov eax,1 ,然后mov ecx,39 )中,然后被存储到返回的Color结构的相关位置(mov[rdx+12],ax 和mov[rdx+10],cx ). 在.NET 7中,它现在生成:// 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
直接将这些常量值赋值到它们的目标位置(mov word ptr [rdx+12],1 和mov word ptr [rdx+10],39 ).其他变化贡献常量折叠包括dotnet/runtime#58171从@SingleAccretion和dotnet/runtime#57605从@SingleAccretion .
然而,一个很大的改进类别来自与传播相关的优化,即前向替换.考虑一下这个不太好的基准测试:[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;
如果我们看一下在.Net 6上为Compute1 生成的汇编代码,它看起来就像我们所希望的那样。我们把Value 相加了5次, Value 被简单地内联并返回一个常量16,所以我们希望为Compute1 生成的汇编代码能够有效地返回值80(十六进制0x50),这正是所发生的:// Program.Compute1() mov eax,50 //内联后为80(16进制是0x50) ret // Total bytes of code 6
但是Compute2 生成汇编代码有点不同.代码的结构是这样的,对SomethingElse 的额外调用最终会略微干扰JIT的分析,而.Net 6最终会得到这样的汇编代码:// Program.Compute2() sub rsp,28 call Program.SomethingElse() add eax,10 //10为16进制16的值 add eax,10 add eax,10 add eax,10 add eax,10 add rsp,28 ret // Total bytes of code 29
而不是单个mov eax,50将值0x50放入返回寄存器,分别为5个单独的add eax, 10生成最终结果0x50(80)值.这个相加的过程是不理想.
事实证明,JIT的许多优化操作的是作为解析IL的一部分创建的树数据结构.在某些情况下,当它们所操作的树更大,包含更多要分析的内容时,优化可以做得更好.但是,各种操作可以将这些树分解为更小的、单独的树,例如使用作为内联一部分创建的临时变量,这样做可以抑制这些操作.为了有效地将这些树组合一起,需要一些东西,那就是正向替换.你可以把正向替换想象成逆向的CSE(公共表达式消除);与尝试查找重复表达式并通过一次计算值并将其存储到临时值中来消除它们不同,正向替换消除了临时值,并有效地将表达式树移动到它的使用站点.显然,如果这样做会否定CSE并导致重复的工作,您就不希望这样做,但是对于只定义一次并使用一次的表达式,这种向前传播是有价值的.
dotnet /runtime#61023添加了一个初始的有限版本的前向替换,然后dotnet /runtime#63720添加了一个更健壮的通用实现.随后,dotnet/runtime#70587对其进行了扩展,使其也涵盖了一些SIMD向量,然后dotnet/runtime#71161对其进行了进一步改进,以支持替换到更多的位置(在本例中为调用实参).有了这些,我们的基准测试现在在.Net 7中生成了以下代码:// Program.Compute2() sub rsp,28 call qword ptr [7FFCB8DAF9A8] add eax,50 //在.Net 6生成汇编代码,需要5次add相加操作,这里直接用5次相加的值 add rsp,28 ret // Total bytes of code 18
个人能力有限,如果您发现有什么不对,请私信我
如果您觉得对您有用的话,可以点个赞或者加个关注,欢迎大家一起进行技术交流
CPU使用率和负载的区别什么是CPU使用率?CPU使用率就是cpu在非空闲状态下的时间占比,它反映了CPU的繁忙程度。在linux环境下,使用top命令查看cpu使用状况,如图us表示cpu在用户态运行的
突发!曾年入710亿,超级巨头被申请破产现金只够发40单月工资国美能迎来转机吗?作者丨铅笔道编辑部2021年2月,满怀抱负走出监狱的黄光裕,可能怎么也想不到,即便是自己出山22个月,也无法重整国美的厄运。今年12月1日,国美被供应商申请破产清
着急变现!巴西队还没夺冠,维尼休斯就要换赞助商了巴西击败韩国进入八强,拥有魔笛的克罗地亚已经开始休整,双方14决战即将打响!今天,巴西媒体突然爆料,球队核心维尼休斯要把耐克踢掉他本人觉得现有的待遇,没有真正体现他的价值其实真正的
(卡塔尔世界杯)足球八分之一决赛葡萄牙对阵瑞士(9)当日,在卡塔尔卢赛尔球场进行的2022卡塔尔世界杯足球赛八分之一决赛中,葡萄牙队对阵瑞士队。12月6日,葡萄牙队球员佩佩(左三)在比赛中头球攻门得分。新华社记者曹灿摄12月6日,葡
卡塔尔世界杯6日综合西班牙点球大战遭淘汰葡萄牙大胜瑞士晋级新华社多哈12月6日电(记者刘旸)卡塔尔世界杯足球赛6日进行最后两场争夺八强的比赛,夺冠热门西班牙队在点球大战中三罚尽失,03不敌摩洛哥队遭淘汰。葡萄牙队冈卡洛拉莫斯完成本届世界杯
柳传快联想和华为(四)曾经,在二零一零年以前,你自己客观公正实事求是用联想的产品多一些?还是使用华为的产品多一些?联想有哪些改变学习娱乐工作生活的产品?华为又有哪些你记得住的产品?这个前提是在二零一零年
140万元直接没了!曝因FF91起火前CEO被下台11月中旬,一辆法拉第FF91在美国测试途中起火被烧毁,现场照片显示,车辆几乎被烧成空壳。而法拉第FF91在美国售价为20万美元,相当于140万直接没了。近日海外媒体爆料,此事件或
下一代电商,可能长什么样图片来源视觉中国电商业态正迎来新的变化。过去几年,内容电商直播电商迎来了高光时刻,消费者在刷短视频看直播浏览文章的过程中,自然而然地进行了消费。但铺天盖地的信息,也在冲击消费者的兴
徐小明周三操作策略今天上午上证指数90分钟顶部钝化消失,加上昨天60分钟和120分钟钝化的消失,至此所有分钟周期的顶部钝化全消失了。钝化消失后市场在近期不会再有结构,因为构筑结构是需要一个过程的,要
贝泰妮董俊姿任何市场的变化,底层逻辑都是抓住消费者2013年,年轻的董俊姿和他的初创团队,迎来了他们人生中的第一个双十一。24小时400万,全公司沸腾了,哇塞!从没见过那么大的数字!贝泰妮集团联合创始人(以下简称贝泰妮)董俊姿兴奋
可随时变身游戏掌机的笔记本!GPDWINMax2是否值得选?GPD和壹号本一样,品牌知名度不高,但旗下的产品却极具特色,在小众的口袋本市场有着不俗的声望。GPDWINMax2,则是该品牌旗下的多面手,既是超便携笔记本,还是掌上游戏机。GPD