Java直接阵列索引访问与循环访问之间的性能差异

时间:2016-11-28 18:37:01

标签: java arrays performance-testing benchmarking

我正在试验谓词。我尝试实现谓词来序列化分布式系统中的问题。我写了一个简单的例子,其中test函数只返回true。我正在测量开销,我偶然发现了这个有趣的问题。与直接访问相比,在for循环中访问数组要慢10倍。

class Test {
    public boolean test(Object o) { return true; }
}

long count = 1000000000l;
Test[] test = new Test[3];
test[0] = new Test();
test[1] = new Test();
test[2] = new Test();
long milliseconds = System.currentTimeMillis();
for(int i = 0; i < count; i++){
    boolean result = true;
    Object object = new Object();
    for(int j = 0; j < test.length; j++){
        result = result && test[j].test(object);
    }
}
System.out.println((System.currentTimeMillis() - milliseconds));
  

但是,以下代码几乎快了10倍。什么可以   原因是什么?

milliseconds = System.currentTimeMillis();
for(int i=0 ; i < count; i++) {
    Object object = new Object();
    boolean result = test[0].test(object) && test[1].test(object) && test[2].test(object);
}
System.out.println((System.currentTimeMillis() - milliseconds));

我的i5基准测试结果。

  • 4567毫秒用于循环访问

  • 直接访问297毫秒

3 个答案:

答案 0 :(得分:1)

如果循环标头需要一个单位时间来执行第一个解决方案循环标头评估需要3N个单位时间。在直接访问时需要N.

除了第一解决方案3&amp;&amp;每次迭代的条件评估,而在第二次只有2。

最后但并非最不重要的布尔短路评估会导致您的第二个更快的例子,即“过早地”停止测试条件,即如果第一个&amp;&amp;和整个结果评估为假。条件结果错误。

答案 1 :(得分:1)

由于test(Object o)的可预测结果,编译器能够非常有效地优化第二段代码。第一段代码中的第二个循环使得这种优化成为不可能。

将结果与以下Test类进行比较:

static class Test {
    public boolean test(Object o) {
        return Math.random() > 0.5;
    }
}  

......和循环:

    long count = 100000000l;
    Test[] test = new Test[3];
    test[0] = new Test();
    test[1] = new Test();
    test[2] = new Test();

    long milliseconds = System.currentTimeMillis();

    for(int i = 0; i < count; i++){
        boolean result = true;
        Object object = new Object();
        for(int j = 0; j < test.length; j++){
            result = result && test[j].test(object);
        }
    }

    System.out.println((System.currentTimeMillis() - milliseconds));
    milliseconds = System.currentTimeMillis();

    for(int i=0 ; i < count; i++) {
        Object object = new Object();
        boolean result = test[0].test(object) && test[1].test(object) && test[2].test(object);
    }

    System.out.println((System.currentTimeMillis() - milliseconds));

现在两个循环几乎需要相同的时间:

run:
3759
3368
BUILD SUCCESSFUL (total time: 7 seconds)

p.s。: 有关JIT编译器优化的更多信息,请查看this article

答案 2 :(得分:1)

您几乎承担了使用微基准测试所能犯的所有基本错误。

  • 通过确保使用计算结果,您无法确保无法优化代码。
  • 你的两个代码分支有微妙但绝对不同的逻辑(正如指出的变量2总是会短路)。由于test()返回常量,第二种情况更容易针对JIT进行优化。
  • 你没有预热代码,邀请JIT优化时间被某处纳入执行时间
  • 您的测试代码未考虑执行测试用例的顺序对测试结果产生影响。它的公平地运行案例1,然后案例2运行相同的数据和对象。 JIT将在案例2运行时优化测试方法并收集有关其行为的运行时统计信息(以案例1的执行时间为代价)。