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;
}
}
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;
}
}
6条答案
按热度按时间ckocjqey1#
位操作几乎肯定更快。
除法/取模是一个 * 通用 * 运算,它必须适用于您提供的任何除数,而不仅仅是2。它还必须检查下溢、范围错误和除以零,并维护余数,所有这些都需要时间。
位操作只执行位“与”操作,在本例中恰好对应于除以2,实际上可能只使用一个处理器操作来执行。
cgh8pdjw2#
要么
&
表达式会更快,要么它们的速度相同,上次我试过了,当我用2的时候它们的速度是一样的(因为编译器可以优化它),但是如果2
在变量中,%
会变慢。作为奇数测试的表达式x % 2 == 1
不适用于负x
。因此,“这至少是优选&
的一个原因。4szc88ey3#
在实践中几乎不会有明显的差别,特别是,很难想象这种指令会成为实际瓶颈的情况。
(Some吹毛求疵:“二进制”运算应称为按位运算,而“模”运算实际上是余数运算)
从更理论化的观点来看,我们可以假设二进制运算比余数运算更有效,其原因在其他答案中已经指出。
不过,再回到实际的观点:JIT几乎肯定会来救援,考虑下面的(非常简单的)测试:
使用热点反汇编程序VM运行它
创建JIT反汇编日志。
实际上,对于模版本的第一次调用,它创建了以下反汇编:
其中
irem
指令被翻译成idiv
,这被认为是相当昂贵的。与此相反,二进制版本使用
and
指令进行决策,正如预期的那样:然而,对于最终的优化版本,两个版本生成的代码更加相似。在这两种情况下,编译器都做了大量的循环展开,但方法的核心仍然可以识别:对于按位版本,它生成包含以下指令的展开循环:
仍然有
and
指令用于测试最低位,但是对于模版本,展开循环的核心是我必须承认,我不能完全理解(至少在合理的时间内不能)它在那里做什么。但无论如何:
irem
字节码指令也用and
汇编指令实现,并且在所得到的机器码中不再有 * 任何 *idiv
指令。因此,重复一下这个答案中的第一句话:实际上几乎不会有明显的差别,不仅因为单个指令的开销几乎不会成为瓶颈,而且因为你永远不知道实际上会执行哪些指令,在这种特殊情况下,你必须假设它们基本上是相等的。
brc7rcf04#
实际上,这两个表达式都没有测试被2整除的可能性(除非是负数),如果x是奇数,它们实际上都解析为
true
。有许多其他的方法来测试偶/奇(例如
((x / 2) * 2) == x)
),但没有一种方法具有x & 1
的最佳特性,这仅仅是因为 * 没有编译器可能会出错并使用除法 *。大多数现代编译器会将
x % 2
编译成与x & 1
相同的代码,但一个特别愚蠢的编译器 * 可能 * 使用除法操作实现x % 2
,因此它 * 可能 * 效率较低。至于哪一个更好,则是另一回事,一个新手或疲惫的程序员可能不认为
x & 1
是测试奇数的,但x % 2
会更清楚,所以x % 2
会是更好的选择。我-我会选择
if ( Maths.isEven(x) )
,让它绝对清楚我的意思。恕我直言,效率排在列表的后面,远远超过清晰度和可读性。7lrncoxx5#
二进制运算更快。求模运算必须计算一次除法才能得到余数。
7cwmlq896#
更多二进制运算选择:
x〉〉1〈〈1!=x
x〉〉1〈〈1^x
在python中测试: