Java编译器或 JIT编译器是否优化除法或乘法,以2的常数幂进行比特转换?
例如,以下两个语句是否被优化为相同?
int median = start + (end - start) >>> 1;
int median = start + (end - start) / 2;
(基本上是this question但是对于Java)
答案 0 :(得分:15)
虽然接受的答案是正确的,因为分裂不能简单地由右移代替,但基准是非常错误的。 运行不到一秒钟的任何Java基准测试可能都会测量解释器的性能 - 而不是你通常关心的事情。
我无法抗拒并写下了自己的benchmark,这主要表明它更加复杂。我不是要完全解释results,但我可以这么说
答案 1 :(得分:9)
不,Java编译器不这样做,因为它无法确定(end - start)
的符号是什么。为什么这很重要?负整数的位移产生与普通除法不同的结果。在这里,您可以看到演示:this simple test:
System.out.println((-10) >> 1); // prints -5
System.out.println((-11) >> 1); // prints -6
System.out.println((-11) / 2); // prints -5
另请注意,我使用>>
代替>>>
。 >>>
是无符号位移,而>>
是签名。
System.out.println((-10) >>> 1); // prints 2147483643
@Mystical:我编写了一个基准测试,表明编译器/ JVM没有进行优化:https://ideone.com/aKDShA
答案 2 :(得分:1)
如果JVM没有这样做,你可以自己轻松完成。
如上所述,负数的右移不会与除法相同,因为结果在错误的方向上四舍五入。如果您知道红利是非负数,则可以通过班次安全地替换除法。如果它可能是否定的,您可以使用以下技术。
如果您能以此表格表达原始代码:
int result = x / (1 << shift);
您可以使用此优化代码替换它:
int result = (x + (x >> 31 >>> (32 - shift))) >> shift;
或者,或者:
int result = (x + ((x >> 31) & ((1 << shift) - 1))) >> shift;
这些公式通过添加从被除数的符号位计算的小数来补偿不正确的舍入。这适用于所有班次值从1到30的任何x
。
如果班次是1(即你除以2),那么可以在第一个公式中删除>> 31
以给出这个非常整洁的片段:
int result = (x + (x >>> 31)) >> 1;
我发现即使换档不恒定,这些技术也会更快,但如果换档不变,它们显然会受益最多。注意:对于long
x
而不是int
,请将31和32分别更改为63和64.
Examining the generated machine code表明(不出所料)HotSpot Server VM可以在转换不变时自动执行此优化,但(也不出所料)HotSpot Client VM过于愚蠢。