【问题标题】:Why is my JVM doing some runtime loop optimization and making my code buggy?为什么我的 JVM 会进行一些运行时循环优化并使我的代码出现错误?
【发布时间】:2013-06-14 12:00:48
【问题描述】:

考虑下面的java代码:

public int main() {
    int i = 1111;

    for (; rules(i) != true && i < Integer.MAX_VALUE; i++) {
        //LOG.debug("Testing i: " + i);
    }

    System.out.println("The mystery number is: " + i);

    return i;
}

protected boolean rules(int nb) {
    //...
}

我发现即使for循环继续求值是true,当循环体为空时,循环也会停止执行。

main 的最终结果是错误的(i 大约 98% 的时间是 16698,有时会更高/更低)。

如果我从循环体中取消注释 LOG 语句,循环将继续运行,直到循环继续评估为 false

我使用的 JVM 是 MacOS X VM 1.6.0。

  • 它是否在进行某种运行时优化
  • 这种运行时优化是否可以视为错误?
  • 或者是否在 Java 规范中的某处说过,继续评估不应运行函数式操作?

ps:完整代码源 + 其单元测试可在此处获得:https://gist.github.com/dirtyhenry/5804130

更新:

  • 我只通过 junit 运行代码。 junit 会为这种行为负责吗?

更新 2:

java -version

返回:

java version "1.6.0_51"
Java(TM) SE Runtime Environment (build 1.6.0_51-b11-456-11M4508)
Java HotSpot(TM) 64-Bit Server VM (build 20.51-b01-456, mixed mode)

【问题讨论】:

  • 正如我的问题底部提到的,完整的实现+我用来调查问题的单元测试在要点gist.github.com/dirtyhenry/5804130中提供
  • 尝试从int切换到long。也许有一个静默值溢出。
  • @spas:有趣的是,当从int 切换到long 并保持一个空循环时,我得到了正确的结果。不过有趣的是,因为 16698 与 Java 中 int 的最大值相差甚远(2147483647,参见docs.oracle.com/javase/tutorial/java/nutsandbolts/…
  • 如果您提及“正确”答案可能会有所帮助。 (如果不是 16 698,那又是什么呢?)另外,你能用更简单的“规则(i)”测试来重现这个吗?
  • 我认为“正确”的答案是 Integer.MAX_VALUE,即当 i 增加到预期的最大值时满足循环条件

标签: java macos jvm runtime compiler-optimization


【解决方案1】:

尝试用任何其他(未注释的)有效语句替换日志记录语句,然后循环也会按预期运行。它绝对是 JVM 代码优化策略。我认为JVM没有理由无缘无故地在循环中循环。

【讨论】:

    【解决方案2】:

    我在 SO 上看到了一些关于循环优化的参考,仅在超过 10000 次迭代后才会发生。也许这就是为什么“幻数”经常在 16000 左右?

    这里有一个很好的讨论

    JVM option to optimize loop statements

    【讨论】:

      【解决方案3】:

      什么都不做循环和条件可以被 JIT 编译器优化掉,这绝对是正在发生的事情。

      【讨论】:

        【解决方案4】:

        在将其转换为常规 Java/主程序(即 public static void main( String[] args ),不要将“主”名称用于其他目的)之后,我已经尝试过了。我已将规则( nb )实施为'return false;'并且它总是停在 2147483647。我也尝试过'return Math.random()

        我认为在您的情况下,rules() 中发生了一些事情,javac 不能仅仅因为主体为空而跳过循环,您在可能想要遍历的条件下编写了代码。我写了这样的循环: while ( DORunThis() );一直运行,直到方法返回 false,如预期的那样。

        顺便说一句:

        !rules (i) 比丑陋的更简洁优雅:rules(i) != true

        也许你并不真正想要:i

        for (; !rules ( i ) && i != Integer.MAX_VALUE; i++ );
        

        它也能够为 MAX_VALUE 评估 rules()。

        【讨论】:

          【解决方案5】:

          您的另一个选择是在块内运行规则测试。 JIT 编译器可能会抛出循环,因为它是空的。试试这样的:

          public int main() {
              int i = 1111;
          
              for (; i < Integer.MAX_VALUE; i++) {
                  boolean completed = !rules(i);
                  if (completed) {
                      break;
                  }
              }
          
              System.out.println("The mystery number is: " + i);
          
              return i;
          }
          

          【讨论】:

            【解决方案6】:

            我在这里遇到了类似的问题:Near empty Java For-Loop acts strange 您应该尝试使用 JVM 1.7 或尝试使用 while 循环:

            public int main() {
                int i = 1111;
            
                while(i < Integer.MAX_VALUE){
                    if (!rules(i)) {
                        break;
                    }
                    i++
                }
            
                System.out.println("The mystery number is: " + i);
            
                return i;
            }
            

            【讨论】:

              【解决方案7】:

              我使用 jdk 1.7.0_21 测试了您的代码,无论是否使用 LOG.debug 语句,它都返回了相同的结果 942210。

                  int i = 0;
                  for( ; !rules(i) && i < Integer.MAX_VALUE ; i++ ){
                      //LOG.debug( "test " + i );
                  }
                  System.out.println( "i is " + i );
              

              我还打印了两个版本的字节码(空循环)

                 0: aload_0       
                 1: invokespecial #3                  // Method java/lang/Object."<init>":()V
                 4: iconst_0      
                 5: istore_1      
                 6: aload_0       
                 7: iload_1       
                 8: invokevirtual #4                  // Method rules:(I)Z
                11: ifne          26
                14: iload_1       
                15: ldc           #5                  // int 2147483647
                17: if_icmpge     26
                20: iinc          1, 1
                23: goto          6
                26: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
                29: new           #7                  // class java/lang/StringBuilder
                32: dup           
                33: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
                36: ldc           #9                  // String i is 
                38: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
                41: iload_1       
                42: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
                45: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
                48: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                51: return 
              

              带有调试打印输出的循环

                 0: aload_0       
                 1: invokespecial #3                  // Method java/lang/Object."<init>":()V
                 4: iconst_0      
                 5: istore_1      
                 6: aload_0       
                 7: iload_1       
                 8: invokevirtual #4                  // Method rules:(I)Z
                11: ifne          48
                14: iload_1       
                15: ldc           #5                  // int 2147483647
                17: if_icmpge     48
                20: new           #6                  // class java/lang/StringBuilder
                23: dup           
                24: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
                27: ldc           #8                  // String test 
                29: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
                32: iload_1       
                33: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
                36: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
                39: invokestatic  #12                 // Method LOG.debug:(Ljava/lang/String;)V
                42: iinc          1, 1
                45: goto          6
                48: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
                51: new           #6                  // class java/lang/StringBuilder
                54: dup           
                55: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
                58: ldc           #14                 // String i is 
                60: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
                63: iload_1       
                64: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
                67: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
                70: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                73: return 
              

              如您所见,循环结构是相同的。 20 - 39 只是构造字符串并调用 LOG.debug。

              所以,这可能是一个 jdk 问题。尝试 1.7,它应该可以工作。

              【讨论】:

                猜你喜欢
                • 2020-11-11
                • 2021-06-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-12-15
                • 1970-01-01
                • 1970-01-01
                • 2023-02-08
                相关资源
                最近更新 更多