为什么编译器优化ldc.i8而不是ldc.r8?

Ste*_*oix 9 .net c# cil compiler-optimization

我想知道为什么这个C#代码

long b = 20;
Run Code Online (Sandbox Code Playgroud)

被编译为

ldc.i4.s 0x14
conv.i8
Run Code Online (Sandbox Code Playgroud)

(因为需要3个字节而不是所需的9个字节ldc.i8 20.有关详细信息,请参阅内容.)

而这段代码

double a = 20;
Run Code Online (Sandbox Code Playgroud)

被编译为9字节指令

ldc.r8 20
Run Code Online (Sandbox Code Playgroud)

而不是这个3字节的序列

ldc.i4.s 0x14
conv.r8
Run Code Online (Sandbox Code Playgroud)

(使用mono 4.8.)

这是错失的机会还是conv.i8代码大小增加失衡的成本?

Lua*_*aan 7

因为float不是一个较小的double,而integer不是float(反之亦然).

所有int值都对值进行1:1映射long.同样根本不是真实floatdouble-浮点运算是棘手的方式.更不用说int-float转换不是免费的 - 不像在栈中/寄存器中推送1字节值; 看看这两种方法产生的x86-64代码,而不仅仅是IL代码.IL代码的大小不是优化中考虑的唯一因素.

这与之相反decimal,实际上是基数为10的十进制数,而不是基数为2的十进制浮点数.有20M完美的映射20,反之亦然,因此编译器可以自由发出:

IL_0000:  ldc.i4.s    0A 
IL_0002:  newobj      System.Decimal..ctor
Run Code Online (Sandbox Code Playgroud)

对于二进制浮点数,同样的方法根本不安全(或便宜!).

您可能认为这两种方法必然是安全的,因为在编译时我们是否从整数文字("字符串")转换为double值,或者我们是否在IL中进行转换并不重要.但事实并非如此,因为一些规范潜水揭晓:

ECMA CLR规范,III.1.1.1:

浮点数(静态,数组元素和类的字段)的存储位置具有固定大小.支持的存储大小为float32和float64.其他地方(在评估堆栈上,作为参数,作为返回类型和作为局部变量)浮点数使用内部浮点类型表示.在每个这样的实例中,变量或表达式的标称类型是float32或float64,但其值可能在内部用额外的范围和/或精度表示.

为了简单起见,让我们假装float64实际上使用4个二进制数字,而实现定义浮点类型(F)使用5个二进制数字.我们想要转换一个恰好具有超过四位数的二进制表示的整数文字.现在比较一下它的表现方式:

ldc.r8 0.1011E2 ; expanded to 0.10110E2
ldc.r8 0.1E2
mul             ; 0.10110E2 * 0.10000E2 == 0.10110E3
Run Code Online (Sandbox Code Playgroud)

conv.r8转换为F,而不是float64.所以我们实际得到:

ldc.i4.s theSameLiteral
conv.r8 ; converted to 0.10111E2
mul     ; 0.10111E2 * 0.10000E2 == 0.10111E3
Run Code Online (Sandbox Code Playgroud)

哎呀:)

现在,我很确定在任何合理的平台上都不会出现0-255范围内的整数.但由于我们是针对CLR规范进行编码,因此我们无法做出这样的假设.JIT编译器可以,但为时已晚.语言编译器可能将两者定义为等效,但C#规范不能 - double本地被认为是float64,而不是F.如果您愿意,可以使用自己的语言.

在任何情况下,IL生成器都没有真正优化.这大部分留给了JIT编译.如果你想要一个优化的C#-IL编译器,写一个 - 我怀疑有足够的好处来保证这一努力,特别是如果你的唯一目标是使IL代码更小.大多数IL二进制文件已经比等效的本机代码小很多.

至于运行的实际代码,在我的机器上,两种方法都会产生完全相同的x86-64程序集 - 从数据段加载一个双精度值.JIT可以轻松进行此优化,因为它知道代码实际运行的体系结构.


Ill*_*ack 3

我怀疑你会得到比“因为没有人认为有必要实施它”更令人满意的答案。

事实上,他们本来可以这样做,但正如 Eric Lippert 多次指出的那样,功能是选择实现的,而不是选择不实现的。int在这种特殊情况下,此功能的收益并没有超过成本,例如额外的测试、和之间的重要转换float,而在 的情况下ldc.i4.s,这并不是那么麻烦。另外,最好不要使用更多优化规则来使抖动膨胀。

如Roslyn 源代码所示,转换仅针对long. 总而言之,也完全有可能为floator添加此功能double,但它不会有多大用处,除非生成较短的 CIL 代码(在需要内联时有用),并且当您想要使用浮点常量时,通常实际上使用浮点数(即不是整数)。