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

2022-09-03 02:43:56

这个问题与这个两个循环体或一个(结果相同)相同,但在我的情况下,我使用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是否<1000000000 20亿次,而如果我将循环融合在一起,则只有10亿次。那里还有其他事情发生吗?

谢谢!


答案 1

如果不运行预热阶段,则可能会优化和编译第一个循环,但不会编译第二个循环,而合并它们时,整个合并循环都会被编译。此外,使用该选项和您的代码,由于您不使用结果,因此大多数都会得到优化。server

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

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

  • 环路 1 = 500ms
  • 环路 2 = 900 ms
  • 合并循环 = 1,300 ms

因此,合并的循环稍微快一些,但不是那么多。

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);
}

答案 2

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

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

CPU可以并行执行大量独立操作(奔腾III上的深度为10,至强上的深度为20)。它尝试并行执行的一个操作是分支,使用分支预测,但如果不是每次都采用相同的分支。

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

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;     
} 

推荐