为什么在Java中(a*b!=0)比(a!=0&b!=0)快?

我正在用Java编写一些代码,其中,在某一点上,程序的流程取决于两个int变量;a「;及;“b”;,为非零(注意:a和b从不为负,也从不在整数溢出范围内)

我可以用它来评估

如果(a!=0&b!=0){/*Some code*/}

或者

如果(a*b!=0){/*Some code*/}

因为我期望这段代码每次运行数百万次,所以我想知道哪一段会更快。我通过在一个巨大的随机生成的数组上比较它们来进行实验,我也很好奇数组的稀疏性(数据分数=0)会如何影响结果:

时间长;
最终整数长度=50000000;
int=0;
int[][]nums=新int[2][len];
对于(双分数=0;分数<=0.9;分数+=0.0078125){
对于(int i=0;i<2;i++){
对于(int j=0;j<len;j++){
double random=Math.random();
如果(随机分数)nums[i][j]=0;
else nums[i][j]=(int)(随机*15+1);
}
}
时间=System.currentTimeMillis();
对于(int i=0;i<len;i++){
如果(/*插入nums[0][i]*nums[1][i]!=0或nums[0][i]!=0&nums[1][i]!=0*/)任意++;
}
System.out.println(System.currentTimeMillis()-time);
}

结果表明,如果你期望;a「;或;b"要大于~3%的时间等于0,a*b!=0a快=0&amp&b=0

我很想知道为什么。有人能解释一下吗?它是编译器还是硬件级的

编辑:出于好奇…现在我了解了分支预测,我想知道模拟比较会显示ab的非零值:

我们确实看到了与预期相同的分支预测效果,有趣的是,该图在X轴上有些翻转

更新

1-我添加了!(a==0 | | b==0)以查看发生了什么

2-我还包括a!=0 | | b!=0(a+b)!=0和(a | b)!=0出于好奇,在了解分支预测后。但是,它们在逻辑上并不等同于其他表达式,因为只有ab需要非零才能返回true,因此不需要对它们进行处理效率比较

3-我还添加了我用于分析的实际基准测试,它只是迭代任意int变量

4-一些人建议包括a!=0&b!=0a!=0&amp&b!=0,预测其行为将更接近a*b!=0,因为我们将删除分支预测效果。我不知道&amp可以用于布尔变量,我认为它只用于整数的二进制操作

注意:在我考虑所有这些的上下文中,int溢出不是一个问题,但在一般上下文中这绝对是一个重要的考虑因素

CPU:Intel Core [email protected]

Java版本:1.8.0_45
Java(TM)SE运行时环境(build 1.8.0_45-b14)
Java HotSpot(TM)64位服务器虚拟机(构建25.45-b02,混合模式)

我忽略了一个问题,即您的基准测试可能有缺陷,而只是从表面上看结果

它是编译器还是硬件级的

对于后者,我认为:

如果(a!=0&b!=0)

将编译为2个内存加载和两个条件分支

如果(a*b!=0)

将编译为2个内存加载,一个乘法和一个条件分支

如果硬件级分支预测无效,则乘法可能比第二个条件分支快。当你增加比例时。。。分支预测的效果越来越差

条件分支速度较慢的原因是它们导致指令执行管道暂停。分支预测是通过预测分支的走向并据此推测选择下一条指令来避免暂停。如果预测失败,则加载另一方向的指令时会有延迟

(注意:以上解释过于简单。要获得更准确的解释,您需要查看CPU制造商为汇编语言编码器和编译器编写者提供的文献。维基百科页面分支预测值是良好的背景。)


然而,有一件事你需要注意这个优化。是否存在a*b!=0会给出错误的答案吗?考虑计算产品导致整数溢出的情况。


更新

你的图表倾向于证实我所说的

  • 在条件分支a*b!=0case,这在图表中显示出来

  • 如果在X轴上将曲线投影到0.9以上,则看起来1)它们将在约1.0处相交,2)相交点的Y值与X=0.0时的Y值大致相同


更新2

我不明白为什么a+b!=0和a | b!=0个案例。分支预测器逻辑中可能有一些聪明的东西。或者它可以表示其他的东西

(请注意,这种情况可能特定于特定的芯片型号,甚至版本。在其他系统上,基准测试的结果可能不同。)

但是,它们都具有适用于ab的所有非负值的优点

发表评论