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

问题:

我在Java中编写一些代码,在某种程度上,程序的流程由两个int变量“a”和“b”是否为非零(请注意:a和b从不为负,而从不在整数溢出范围内)。
我可以用它来评估

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

或者替代地

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

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

long time;
final int len = 50000000;
int arbitrary = 0;
int[][] nums = new int[2][len];

for (double fraction = 0 ; fraction <= 0.9 ; fraction += 0.0078125) {
    for(int i = 0 ; i < 2 ; i++) {
        for(int j = 0 ; j < len ; j++) {
            double random = Math.random();

            if(random < fraction) nums&#91;i&#93;&#91;j&#93; = 0;
            else nums&#91;i&#93;&#91;j&#93; = (int) (random*15 + 1);
        }
    }

    time = System.currentTimeMillis();

    for(int i = 0 ; i < len ; i++) {
        if( /*insert nums&#91;0&#93;&#91;i&#93;*nums&#91;1&#93;&#91;i&#93;!=0 or nums&#91;0&#93;&#91;i&#93;!=0 &amp;&amp; nums&#91;1&#93;&#91;i&#93;!=0*/ ) arbitrary++;
    }
    System.out.println(System.currentTimeMillis() - time);
}
&#91;/code&#93;
结果表明,如果您希望“a”或“b”等于0〜3%的时间,则<code>a*b != 0</code>比<code>a!=0 &amp;&amp; b!=0</code>快,
 <a href="https://i.stack.imgur.com/POHYD.png"><img alt="Graphical graph of the results of a AND b non-zero" src="https://i.stack.imgur.com/POHYD.png"/></a>
我很好奇知道为什么有人会有光吗?是编译器还是在硬件级别?
 <strong>编辑:</strong> <em>Out of curiosity...</em>现在我了解了分支预测,我想知道一个<strong>要么</strong> b的模拟比较会显示为非零:
 <a href="https://i.stack.imgur.com/GpJoM.png"><img alt="Graph of a or b non-zero" src="https://i.stack.imgur.com/GpJoM.png"/></a>
我们确实看到与预期相同的分支预测效果,有趣的是,图形沿X轴有些翻转。
<h3>更新</h3>
1-我添加<code>!(a==0 || b==0)</code>到分析,看看会发生什么。
在学习分支预测之后,我还包括<code>a != 0 || b != 0</code>,<code>(a+b) != 0</code>和<code>(a|b) != 0</code>出于好奇心。但是它们在逻辑上并不等同于其他表达式,因为只有<em>OR</em> b需要非零才能返回true,所以它们并不是为了比较处理效率。
我也添加了我用于分析的实际基准,这只是迭代一个任意的int变量。
有些人建议包括<code>a != 0 &amp; b != 0</code>而不是<code>a != 0 &amp;&amp; b != 0</code>,预测它将更加接近<code>a*b != 0</code>,因为我们将删除分支预测效果。我不知道<code>&amp;</code>可以与布尔变量一起使用,我以为它只用于具有整数的二进制操作。
注意:在我正在考虑这一切的上下文中,int溢出不是一个问题,但这在一般情况下绝对是一个重要的考虑。
CPU:Intel Core i7-3610QM @ 2.3GHz
Java版本:1.8.0_45 <br/>
Java(TM)SE运行时环境(build 1.8.0_45-b14)<br/>
Java HotSpot(TM)64位服务器虚拟机(构建25.45-b02,混合模式)

<h4>回答:</h4>
我忽略了您的基准测试<em>might</em>有缺陷的问题,并将结果作为面值。
<blockquote>
是编译器还是在硬件级别?
</blockquote>
后者,我认为:

  if (a != 0 &amp;&amp; b != 0)

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

  if (a * b != 0)

将编译为2个内存加载,一个乘法和一个条件分支。
如果硬件级分支预测无效,乘法可能比第二条件分支更快。当你增加比例...分支预测变得不那么有效。
条件分支较慢的原因是它们导致指令执行流水线停顿。分支预测是通过预测分支将要去哪方式来避免失速,并且基于此推测选择下一条指令。如果预测失败,则在加载其他方向的指令时存在延迟。
(注意:上面的说明过于简单,为了更准确的解释,您需要查看CPU制造商为汇编语言编码器和编译器编写者提供的文献,Branch Predictors上的维基百科页面是很好的背景。
但是,有一件事你需要注意这个优化。有没有值a * b != 0会给出错误的答案?考虑计算产品的整数溢出情况。
 UPDATE
你的图表往往会确认我说的话。

  • 在条件分支a * b != 0情况下也有一个“分支预测”效应,这在图中出现。
  • 如果您在X轴上投影曲线超过0.9,则看起来像1)它们将在约1.0和2处满足),会合点将与X = 0.0大致相同的Y值。

 更新2
我不明白为什么a + b != 0a | b != 0个曲线的曲线不同。在分支预测逻辑中有could be聪明的东西。或者它可以指示别的东西。
(请注意,这种事情可以针对特定的芯片型号甚至版本,其他系统的基准测试结果可能会有所不同)
然而,它们都具有处理ab的所有非负值的优点,

 
 
Code问答: http://codewenda.com/topics/python/
Stackoverflow: Why is (a*b != 0) faster than (a != 0 && b != 0) in Java?

*转载请注明本文链接以及stackoverflow的英文链接

发表评论

电子邮件地址不会被公开。 必填项已用*标注

35 − 26 =