long b = 20;
编译为
ldc.i4.s 0x14
conv.i8
由于使用 ldc.i8 20
需要9个字节,而使用三个字节的ldc.i4.s 20
更为高效。详见这里。
代码示例:
double a = 20;
编译为9字节指令
ldc.r8 20
替换这个由3个字节组成的序列
ldc.i4.s 0x14
conv.r8
(使用mono 4.8。)
这是错过的机会还是
conv.i8
的成本超过了代码大小的收益?long b = 20;
编译为
ldc.i4.s 0x14
conv.i8
由于使用 ldc.i8 20
需要9个字节,而使用三个字节的ldc.i4.s 20
更为高效。详见这里。
代码示例:
double a = 20;
编译为9字节指令
ldc.r8 20
替换这个由3个字节组成的序列
ldc.i4.s 0x14
conv.r8
conv.i8
的成本超过了代码大小的收益?IL_0000: ldc.i4.s 0A
IL_0002: newobj System.Decimal..ctor
为了简化问题,假设 float64 实际上使用 4 个二进制数字,而实现定义的浮动类型(F)使用 5 个二进制数字。我们想要转换一个整数文字,其具有超过四个数字的二进制表示。现在比较一下它将如何行为:存储浮点数(静态、数组元素和类字段)的存储位置是固定大小的。支持的存储大小是 float32 和 float64。 在其他地方(评估堆栈、参数、返回类型和本地变量中),使用内部浮点类型表示浮点数。在每个这样的实例中,变量或表达式的名义类型为 float32 或 float64,但其值可能在内部使用额外的范围和/或精度表示。
ldc.r8 0.1011E2 ; expanded to 0.10110E2
ldc.r8 0.1E2
mul ; 0.10110E2 * 0.10000E2 == 0.10110E3
conv.r8
将转换为F而不是float64。所以我们实际上得到:
ldc.i4.s theSameLiteral
conv.r8 ; converted to 0.10111E2
mul ; 0.10111E2 * 0.10000E2 == 0.10111E3
哎呀 :)
现在,我非常确定,在任何合理的平台上,0-255范围内的整数都不会出现这种情况。但由于我们正在针对CLR规范进行编码,因此我们不能做出这样的假设。JIT编译器可以做到,但那时已经太晚了。语言编译器可能定义这两者是等价的,但C#规范并没有这样做——一个double
局部变量被认为是float64,而不是F。如果您想这样做,您可以自己制作语言。
无论如何,IL生成器实际上并没有进行太多优化。这在很大程度上留给JIT编译。如果您想要一个优化的C#-IL编译器,请编写一个——我怀疑是否有足够的利益来证明这种努力,特别是如果您唯一的目标是使IL代码更小。大多数IL二进制文件已经比等效本机代码小得多。
至于实际运行的代码,在我的计算机上,这两种方法产生完全相同的x86-64汇编——从数据段加载双精度值。JIT可以轻松地进行此优化,因为它知道代码实际上是在哪种架构上运行的。
d %1 == 0
一样简单,我认为与加载长整型的优化没有太大区别,检查是 l < 256
... - Stephane Delcroixconv.r8
是免费的。本文底部的问题是:“这是错失的机会,还是 conv.i8 的成本超过了代码大小的收益?” - Stephane Delcroixlong
完成。总的来说,也完全可以为float
或double
添加此功能,但除了在生成较短的CIL代码(需要内联时有用)和当您想要使用浮点常数时,通常实际上使用浮点数(即不是整数)时,它并没有太多用处。ldc.i4.s
可以处理介于-128到127之间的整数,所有这些整数都可以在float32
中精确表示。然而,CIL在一些存储位置上使用了一个名为F
的内部浮点类型。ECMA-335标准在III.1.1.1中说:
这意味着任何...变量或表达式的名义类型是
float32
或float64
之一...内部表示应具有以下特征:
- 内部表示的精度和范围应大于或等于名义类型。
- 对内部表示的转换应保留值。
float32
值都可以保证在F
中安全地表示,无论F
是什么。ldc.r8 20
时,您引用的链接中给出的答案很好地解释了使用长指令的影响。ldc.i4.s 0x14
conv.r8
我们可以做出一个合理的假设,这对于任何优化JIT编译器都是合理的。我们将假设JIT能够识别这样的指令序列,以便将两个指令一起编译。编译器被赋予了用二进制补码格式表示的值0x14,必须将其转换为float32
格式(如上所述,这始终是安全的)。在相对现代的架构上,这可以非常高效地完成。这种微小的开销是JIT时间的一部分,因此只发生一次。生成的本机代码质量对于两个IL序列都是相同的。
因此,9字节序列存在大小问题,可能会产生从零到更多的任意开销(假设我们在所有地方都使用它),而3字节序列则具有一次性微小的转换开销。哪个更好呢?嗯,有人必须进行一些科学可靠的实验来测量性能差异来回答这个问题。我想强调的是,除非您是编译器优化工程师或研究人员,否则您不应该关心这个问题。否则,您应该在更高级别(源代码级别)优化您的代码。