17

这个问题与这 两个循环体或一个(结果相同)相同 ,但在我的情况下,我使用 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 亿次。里面还有什么事情吗?

谢谢!

4

4 回答 4

6

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

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

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

  • 循环 1 = 500 毫秒
  • 循环 2 = 900 毫秒
  • 合并循环 = 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);
}
于 2012-08-25T16:01:15.217 回答
2

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

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

CPU 可以并行执行大量独立操作(Pentium III 深度为 10,Xeon 深度为 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;     
} 
于 2012-08-26T07:16:31.827 回答
1

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

于 2012-08-25T15:54:08.000 回答
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-是坏还是好

于 2012-08-25T16:00:24.603 回答