在Java中编译循环

时间:2013-04-11 10:37:05

标签: java compilation javac

我可以从JVM specification看到这段代码:

void spin() {
    int i;
    for (i = 0; i < 100; i++) {
        ;    // Loop body is empty
    }
}

应汇编成:

0   iconst_0
1   istore_1
2   goto 8
5   iinc 1 1
8   iload_1
9   bipush 100
11  if_icmplt 5
14  return  

其中条件检查if_icmplt在循环体之后,但是当我自己编译并使用javap查看时,我看到:

0:   iconst_0
1:   istore_1
2:   iload_1
3:   bipush  100
5:   if_icmpge       14
8:   iinc    1, 1
11:  goto    2
14:  return

并且循环条件在循环体之前。为什么会这样?

在身体之后放置条件阻止我们在每个周期之后做goto并且对我来说是合乎逻辑的。那么为什么OracleJDK会采取另一种方式呢?

3 个答案:

答案 0 :(得分:2)

这不是为了更好的JIT优化 - 对于JIT,这些代码片段是等效的。这是因为在javac中进行优化是没有意义的,因为JIT优化无论如何都更强大。

答案 1 :(得分:2)

实现for循环(来自其JLS语义)的简单方法就是这样:

  1. 初始化
  2. 条件 - 如果为false,请转到6.
  3. loop body
  4. 递增
  5. 转到2
  6. 这实际上就是编译器在你的情况下生成的内容:

    1. 初始化

      0:   iconst_0
      1:   istore_1
      
    2. 条件 - 如果为false,请转到6.

      2:   iload_1
      3:   bipush  100
      5:   if_icmpge       14
      
    3. 循环体(空)

    4. 递增

      8:   iinc    1, 1
      
    5. 转到2

      11:  goto    2
      
    6. 14:  return
      
    7. JVM规范中的版本是实现它的另一种方式,这也是可以接受的。 正如回答者所说,普通虚拟机的JIT编译器会再次查看并优化它(如果足够相关),所以对字节码级别的轻微优化只对没有JIT的JVM有效。按指令解释字节码指令。即便如此,任何优化都将特定于实际机器。

答案 2 :(得分:1)

它的指令数量相同,并且已经指出,规范并没有将编译器绑定到这个特定的字节码:

  

编译器可能编译spin到...

编译器几乎可以选择将其编译为它想要的任何字节码,只要最终效果相同。

  

在身体之后放置条件阻止我们在每个周期之后做goto并且对我来说是合乎逻辑的。那么为什么OracleJDK会采取另一种方式呢?

除非编写那个编译器的那个人摇摆不定,否则可能无法说清楚 - 但是我想象你的理论可能是正确的,因为似乎有可能帮助后来的JIT优化。我唯一的预感(可能是不正确的)是它可能与goto命令的定位有关 - 如果将前6条指令作为逻辑块,可能会有更多可能更好的代码内联,没有任何东西,因为它们在编译器实际产生的字节码中。当然,由于特定goto只能跳到该块内,因此没有逻辑差异,JIT仍然可以用完全相同的效果内联它。这些天我想象它能够巧妙地解决这个问题,但它可能并不总是如此,在这种情况下,生成的代码提供了一个很好的解决方案。当然,当(如果)JIT变得足够聪明时,就没有必要更改代码了,所以它就卡住了。

一个精心设计的理论,也许完全错误 - 但如果它是一个功能差异,这是我最好的猜测!

另一种可能性是,编译器的这一部分是如何编写的,完全是巧合的,根本就没有固定的理由(因为编译器开发人员可能没有&# 39; t 尝试使代码与示例中的代码完全相同。)