【问题标题】:Two operations in one loop vs two loops performing the same operations one per loop一个循环中的两个操作与执行相同操作的两个循环每个循环一个
【发布时间】:2012-08-25 15:48:12
【问题描述】:

此问题与此相同 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

谢谢!

【问题讨论】:

  • 我认为这是因为您正在执行 1B 的增量、1B 的比较和 1B 的跳转...
  • int t; 声明移到循环外,只在循环内进行赋值t = a ^ i;t = b ^ i; 会有什么影响?
  • @barrowc 它不会有任何效果。 JIT 的第一个阶段是将 AST 图转换为单一赋值表示,这将撤消这种别名,以便更好地进行生命周期分析。
  • FWIW,它可以去任何一种方式:Why is one loop so much slower than two loops?

标签: java performance loops


【解决方案1】:

如果您不运行预热阶段,则第一个循环可能会得到优化和编译,而第二个则不会,而当您合并它们时,整个合并的循环都会被编译。此外,使用 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);
}

【讨论】:

    【解决方案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;     
    } 
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        您是否使用了-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

        【讨论】:

        • 感谢微基准测试的链接。我的代码中没有循环预热。结果 2 secs/4 secs 是从同一个 JVM 获得的,它是多次试验的平均值。我也没有使用 -server 标志。
        猜你喜欢
        • 1970-01-01
        • 2020-10-17
        • 2019-03-08
        • 1970-01-01
        • 1970-01-01
        • 2023-03-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多