有两种方法可以检查数字是否可被2整除:
x % 2 == 1
(x & 1) == 1
两者中哪一个效率更高?
答案 0 :(得分:5)
位操作几乎肯定更快。
除法/模数是广义操作,它必须适用于您提供的任何除数,而不仅仅是2.它还必须检查下溢,范围错误和除以零,并保持余数,所有这需要时间。
位操作只是做了一点“和”操作,在这种情况下恰好相应于除以2。它实际上可能只使用一个处理器操作来执行。
答案 1 :(得分:2)
&
表达式会更快或速度相同。上次我试过,当我使用文字2时(因为编译器可以优化它),它们的速度是相同的,但如果%
在变量中,则2
会慢一些。
作为奇数测试的表达式x % 2 == 1
不适用于否定x
。
所以至少有一个理由更喜欢&
。
答案 2 :(得分:1)
实际上,这两个表达式都没有用两个方法来测试可分性(除了否定之外)。如果 x为奇数,它们实际上都会解析为true
。
还有许多其他测试偶数/奇数的方法(例如((x / 2) * 2) == x)
),但它们都没有x & 1
的最佳属性,因为没有编译器可能会错误并使用分割
大多数现代编译器会将x % 2
编译为与x & 1
相同的代码,但是一个特别愚蠢的可能使用除法运算来实现x % 2
所以它可能效率低下。
关于更好的争论是另一回事。一个新手/累了的程序员可能不会认为x & 1
是对奇数的测试,但是x % 2
会更清楚,所以有一个论点x % 2
会是更好的选择。
我 - 我去if ( Maths.isEven(x) )
明确表达我的意思。恕我直言效率在列表中方式,远远超过清晰度和可读性。
public class Maths {
// Method is final to encourage compiler to inline if it is bright enough.
public static final boolean isEven(long n) {
/* All even numbers have their lowest-order bit set to 0.
* This `should` therefore be the most efficient way to recognise
* even numbers.
* Also works for negative numbers.
*/
return (n & 1) == 0;
}
}
答案 3 :(得分:1)
在实践中几乎没有明显的差异。特别是,很难想象这样的指令会成为实际的瓶颈。
(一些挑剔:“二进制”操作应该被称为按位操作,而“模数”操作实际上是余数操作)子>
从更理论的角度来看,人们可以假设二元运算比其余运算更有效,原因已在其他答案中指出过。
然而,再次回到实际的观点:JIT几乎肯定会来救援。考虑以下(非常简单)测试:
class BitwiseVersusMod
{
public static void main(String args[])
{
for (int i=0; i<10; i++)
{
for (int n=100000; n<=100000000; n*=10)
{
long s0 = runTestBitwise(n);
System.out.println("Bitwise sum "+s0);
long s1 = runTestMod(n);
System.out.println("Mod sum "+s1);
}
}
}
private static long runTestMod(int n)
{
long sum = 0;
for (int i=0; i<n; i++)
{
if (i % 2 == 1)
{
sum += i;
}
}
return sum;
}
private static long runTestBitwise(int n)
{
long sum = 0;
for (int i=0; i<n; i++)
{
if ((i & 1) == 1)
{
sum += i;
}
}
return sum;
}
}
使用
使用Hotspot反汇编程序VM运行它java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly BitwiseVersusMod
创建JIT反汇编日志。
实际上,对于模数版本的第一次调用,它会创建以下反汇编:
...
0x00000000027dcae6: cmp $0xffffffff,%ecx
0x00000000027dcae9: je 0x00000000027dcaf2
0x00000000027dcaef: cltd
0x00000000027dcaf0: idiv %ecx ;*irem
; - BitwiseVersusMod::runTestMod@11 (line 26)
; implicit exception: dispatches to 0x00000000027dcc18
0x00000000027dcaf2: cmp $0x1,%edx
0x00000000027dcaf5: movabs $0x54fa0888,%rax ; {metadata(method data for {method} {0x0000000054fa04b0} 'runTestMod' '(I)J' in 'BitwiseVersusMod')}
0x00000000027dcaff: movabs $0xb0,%rdx
....
其中irem
指令被转换为idiv
,这被认为是相当昂贵的。
与此相反,二进制版本使用and
指令进行决策,如预期的那样:
....
0x00000000027dc58c: nopl 0x0(%rax)
0x00000000027dc590: mov %rsi,%rax
0x00000000027dc593: and $0x1,%eax
0x00000000027dc596: cmp $0x1,%eax
0x00000000027dc599: movabs $0x54fa0768,%rax ; {metadata(method data for {method} {0x0000000054fa0578} 'runTestBitwise' '(I)J' in 'BitwiseVersusMod')}
0x00000000027dc5a3: movabs $0xb0,%rbx
....
但是,对于最终的优化版本,生成的代码对于两个版本更相似。在这两种情况下,编译器都会进行大量的循环展开,但仍然可以识别方法的核心:对于按位版本,它会生成一个包含以下指令的展开循环:
...
0x00000000027de2c7: mov %r10,%rax
0x00000000027de2ca: mov %r9d,%r11d
0x00000000027de2cd: add $0x4,%r11d ;*iinc
; - BitwiseVersusMod::runTestBitwise@21 (line 37)
0x00000000027de2d1: mov %r11d,%r8d
0x00000000027de2d4: and $0x1,%r8d
0x00000000027de2d8: cmp $0x1,%r8d
0x00000000027de2dc: jne 0x00000000027de2e7 ;*if_icmpne
; - BitwiseVersusMod::runTestBitwise@13 (line 39)
0x00000000027de2de: movslq %r11d,%r10
0x00000000027de2e1: add %rax,%r10 ;*ladd
; - BitwiseVersusMod::runTestBitwise@19 (line 41)
...
仍有and
指令用于测试最低位。但对于模数版本,展开循环的核心是
...
0x00000000027e3a0a: mov %r11,%r10
0x00000000027e3a0d: mov %ebx,%r8d
0x00000000027e3a10: add $0x2,%r8d ;*iinc
; - BitwiseVersusMod::runTestMod@21 (line 24)
0x00000000027e3a14: test %r8d,%r8d
0x00000000027e3a17: jl 0x00000000027e3a2e ;*irem
; - BitwiseVersusMod::runTestMod@11 (line 26)
0x00000000027e3a19: mov %r8d,%r11d
0x00000000027e3a1c: and $0x1,%r11d
0x00000000027e3a20: cmp $0x1,%r11d
0x00000000027e3a24: jne 0x00000000027e3a2e ;*if_icmpne
; - BitwiseVersusMod::runTestMod@13 (line 26)
...
我必须承认,我无法完全理解(至少在合理的时间内)它正在做什么完全。但在任何情况下:irem
字节码指令也使用and
汇编指令实现,并且在生成的机器代码中不再有任何 idiv
指令
所以重复这个答案的第一个陈述:在实践中几乎没有明显的差别。不仅因为单个指令的成本几乎不会成为瓶颈,而且因为你永远不知道实际执行哪些指令,在这种特殊情况下,你必须假设它们基本上是相等的。
答案 4 :(得分:0)
二进制操作更快。 mod操作必须计算除法以获得余数。