【发布时间】:2016-06-02 13:33:10
【问题描述】:
我正在用 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[i][j] = 0;
else nums[i][j] = (int) (random*15 + 1);
}
}
time = System.currentTimeMillis();
for(int i = 0 ; i < len ; i++) {
if( /*insert nums[0][i]*nums[1][i]!=0 or nums[0][i]!=0 && nums[1][i]!=0*/ ) arbitrary++;
}
System.out.println(System.currentTimeMillis() - time);
}
而且结果表明,如果您期望“a”或“b”在大约 3% 以上的时间中等于 0,a*b != 0 比 a!=0 && b!=0 快:
我很想知道为什么。任何人都可以解释一下吗?是编译器还是硬件级别的?
编辑: 出于好奇...... 现在我了解了分支预测,我想知道模拟比较对于 OR 会显示什么strong> b 非零:
我们确实看到了与预期相同的分支预测效果,有趣的是,图表有点沿 X 轴翻转。
更新
1- 我在分析中添加了!(a==0 || b==0),看看会发生什么。
2- 在了解了分支预测之后,出于好奇,我还加入了 a != 0 || b != 0、(a+b) != 0 和 (a|b) != 0。但是它们在逻辑上并不等同于其他表达式,因为只有a OR b 需要非零才能返回true,因此它们不用于比较处理效率。
3- 我还添加了用于分析的实际基准,它只是迭代一个任意 int 变量。
4- 有人建议包含 a != 0 & b != 0 而不是 a != 0 && b != 0,并预测它的行为会更接近 a*b != 0,因为我们将移除分支预测效应。我不知道& 可以与布尔变量一起使用,我以为它只用于整数的二进制操作。
注意:在我考虑所有这些的上下文中,int 溢出不是问题,但在一般上下文中这绝对是一个重要的考虑因素。
CPU:英特尔酷睿 i7-3610QM @ 2.3GHz
Java 版本:1.8.0_45
Java(TM) SE 运行时环境(内部版本 1.8.0_45-b14)
Java HotSpot(TM) 64 位服务器 VM(内部版本 25.45-b02,混合模式)
【问题讨论】:
-
if (!(a == 0 || b == 0))怎么样?众所周知,微基准测试不可靠,这不太可能真正可衡量(对我来说,~3% 听起来像是一个误差范围)。 -
或
a != 0 & b != 0。 -
如果预测分支错误,则分支会变慢。
a*b!=0少了一个分支 -
(1<<16) * (1<<16) == 0但两者都不为零。 -
@Gene:您建议的优化无效。即使忽略溢出,如果
a和b中的一个 为零,则a*b为零;a|b仅当两者都为零时才为零。
标签: java performance processing-efficiency microbenchmark branch-prediction