一个循环中的两个操作与两个循环在每个循环中执行相同的操作

时间:2012-08-25 15:48:13

标签: java performance loops

这个问题与此相同 Two loop bodies or one (result identical) 但在我的情况下,我使用Java。

我有两个循环,运行十亿次。

int a = 188, b = 144, aMax = 0, bMax = 0;

for (int i = 0; i < 1000000000; i++) {
  int t = a ^ i;
  if (t > aMax) 
    aMax = t;     
}  

for (int i = 0; i < 1000000000; i++) {
  int t = b ^ i;
  if (t > bMax) 
    bMax = t;     
}  

在我的机器上运行这两个循环所需的时间是4秒。当我将这两个循环融合到一个循环中并在该单循环中执行所有操作时,它将在2秒内运行。正如您所看到的那样,琐碎的操作构成了循环内容,因此需要恒定的时间。

我的问题是我在哪里获得这种性能提升?

我猜测性能在两个独立循环中受影响的唯一可能的地方是它增加i并检查i&lt; 10亿次,如果我将环融合在一起,只有10亿次。那里还有其他事吗?

谢谢!

4 个答案:

答案 0 :(得分:5)

如果你没有运行预热阶段,第一个循环可能会被优化和编译而不是第二个循环,而当你合并它们时,整个合并循环就会被编译。此外,使用server选项和您的代码,大多数都会在您不使用结果时进行优化。

我已经运行了下面的测试,将每个循环以及合并循环放在他们自己的方法中,并热化JVM以确保所有内容都被编译。

结果(JVM选项:-server -XX:+PrintCompilation):

  • loop 1 = 500ms
  • loop 2 = 900 ms
  • 合并循环= 1,300毫秒

所以合并后的循环稍快一点,但不是那么多。

public static void main(String[] args) throws InterruptedException {

    for (int i = 0; i < 3; i++) {
        loop1();
        loop2();
        loopBoth();
    }

    long start = System.nanoTime();

    loop1();

    long end = System.nanoTime();
    System.out.println((end - start) / 1000000);

    start = System.nanoTime();
    loop2();
    end = System.nanoTime();
    System.out.println((end - start) / 1000000);

    start = System.nanoTime();
    loopBoth();
    end = System.nanoTime();
    System.out.println((end - start) / 1000000);
}

public static void loop1() {
    int a = 188, aMax = 0;
    for (int i = 0; i < 1000000000; i++) {
        int t = a ^ i;
        if (t > aMax) {
            aMax = t;
        }
    }
    System.out.println(aMax);
}

public static void loop2() {
    int b = 144, bMax = 0;
    for (int i = 0; i < 1000000000; i++) {
        int t = b ^ i;
        if (t > bMax) {
            bMax = t;
        }
    }
    System.out.println(bMax);
}

public static void loopBoth() {
    int a = 188, b = 144, aMax = 0, bMax = 0;

    for (int i = 0; i < 1000000000; i++) {
        int t = a ^ i;
        if (t > aMax) {
            aMax = t;
        }
        int u = b ^ i;
        if (u > bMax) {
            bMax = u;
        }
    }
    System.out.println(aMax);
    System.out.println(bMax);
}

答案 1 :(得分:2)

简而言之,CPU可以并行执行合并循环中的指令,从而使性能提高一倍。

第二个循环也可能无法有效优化。这是因为第一个循环将触发整个方法进行编译,第二个循环将被编译而没有任何可能扰乱第二个循环时序的指标。我会将每个循环放在一个单独的方法中,以确保不是这种情况。

CPU可以并行执行大量独立操作(depth 10 on Pentium III and 20 in the Xeon)。它尝试并行执行的一个操作是使用分支预测的分支,但是如果它几乎不是每次都使用相同的分支。

我怀疑循环展开你的循环看起来更像是跟随(在这种情况下可能更多的循环展开)

for (int i = 0; i < 1000000000; i += 2) {
  // this first block is run almost in parallel
  int t1 = a ^ i;
  int t2 = b ^ i;
  int t3 = a ^ (i+1);
  int t4 = b ^ (i+1);
  // this block run in parallel
  if (t1 > aMax) aMax = t1;     
  if (t2 > bMax) bMax = t2;     
  if (t3 > aMax) aMax = t3;     
  if (t4 > bMax) bMax = t4;     
} 

答案 2 :(得分:1)

在我看来,在单循环的情况下,JIT 可以选择进行循环展开,因此性能稍好一些

答案 3 :(得分:1)

你用过-server吗?如果不是,你应该 - 客户端JIT不是可预测的,也不是那么好。如果您真正对正在发生的事情感兴趣,可以使用UnlockDiagnostic + LogCompilation来检查在两种情况下应用的优化(一直到生成的程序集)。

此外,从您提供的代码中我无法看到您是否进行预热,无论是为同一个JVM运行一次还是多次运行,是否进行了几次运行(不同的JVM)。无论你是考虑最佳,平均还是中位时间,你都会抛弃异常值吗?

以下是编写Java微基准测试主题的良好链接:http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html

编辑:还有一个微基准测试技巧,请注意堆叠替换:http://www.azulsystems.com/blog/cliff/2011-11-22-what-the-heck-is-osr-and-why-is-it-bad-or-good