JIT不会优化涉及Integer.MAX_VALUE的循环

在写另一个问题的答案时,我注意到一个奇怪的边界情况下JIT优化。

以下程序不是 “Microbenchmark”, 并非旨在可靠地衡量执行时间(正如在其他问题的答案中指出的那样)。 它只是作为一个MCVE来重现这个问题:

class MissedLoopOptimization { public static void main(String args[]) { for (int j=0; j<3; j++) { for (int i=0; i<5; i++) { long before = System.nanoTime(); runWithMaxValue(); long after = System.nanoTime(); System.out.println("With MAX_VALUE : "+(after-before)/1e6); } for (int i=0; i<5; i++) { long before = System.nanoTime(); runWithMaxValueMinusOne(); long after = System.nanoTime(); System.out.println("With MAX_VALUE-1 : "+(after-before)/1e6); } } } private static void runWithMaxValue() { final int n = Integer.MAX_VALUE; int i = 0; while (i++ < n) {} } private static void runWithMaxValueMinusOne() { final int n = Integer.MAX_VALUE-1; int i = 0; while (i++ < n) {} } } 

它基本上运行相同的循环, while (i++ < n){} ,其中极限n一次设置为Integer.MAX_VALUE ,一次为Integer.MAX_VALUE-1

在Win7 / 64上使用JDK 1.7.0_21和

 java -server MissedLoopOptimization 

计时结果如下:

 ... With MAX_VALUE : 1285.227081 With MAX_VALUE : 1274.36311 With MAX_VALUE : 1282.992203 With MAX_VALUE : 1292.88246 With MAX_VALUE : 1280.788994 With MAX_VALUE-1 : 6.96E-4 With MAX_VALUE-1 : 3.48E-4 With MAX_VALUE-1 : 0.0 With MAX_VALUE-1 : 0.0 With MAX_VALUE-1 : 3.48E-4 

显然,对于MAX_VALUE-1的情况来说,JIT可以做到的:它检测到循环是无用的,并且完全消除它。 但是,它在运行到MAX_VALUE不会删除该循环。

这个观察结果通过查看JIT组件的输出来确认

 java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly MissedLoopOptimization 

该日志包含以下程序集,运行MAX_VALUE

 Decoding compiled method 0x000000000254fa10: Code: [Entry Point] [Verified Entry Point] [Constants] # {method} &apos;runWithMaxValue&apos; &apos;()V&apos; in &apos;MissedLoopOptimization&apos; # [sp+0x20] (sp of caller) 0x000000000254fb40: sub $0x18,%rsp 0x000000000254fb47: mov %rbp,0x10(%rsp) ;*synchronization entry ; - MissedLoopOptimization::runWithMaxValue@-1 (line 29) 0x000000000254fb4c: mov $0x1,%r11d 0x000000000254fb52: jmp 0x000000000254fb63 0x000000000254fb54: nopl 0x0(%rax,%rax,1) 0x000000000254fb5c: data32 data32 xchg %ax,%ax 0x000000000254fb60: inc %r11d ; OopMap{off=35} ;*goto ; - MissedLoopOptimization::runWithMaxValue@11 (line 30) 0x000000000254fb63: test %eax,-0x241fb69(%rip) # 0x0000000000130000 ;*goto ; - MissedLoopOptimization::runWithMaxValue@11 (line 30) ; {poll} 0x000000000254fb69: cmp $0x7fffffff,%r11d 0x000000000254fb70: jl 0x000000000254fb60 ;*if_icmpge ; - MissedLoopOptimization::runWithMaxValue@8 (line 30) 0x000000000254fb72: add $0x10,%rsp 0x000000000254fb76: pop %rbp 0x000000000254fb77: test %eax,-0x241fb7d(%rip) # 0x0000000000130000 ; {poll_return} 0x000000000254fb7d: retq 0x000000000254fb7e: hlt 0x000000000254fb7f: hlt [Exception Handler] [Stub Code] 0x000000000254fb80: jmpq 0x000000000254e820 ; {no_reloc} [Deopt Handler Code] 0x000000000254fb85: callq 0x000000000254fb8a 0x000000000254fb8a: subq $0x5,(%rsp) 0x000000000254fb8f: jmpq 0x0000000002528d00 ; {runtime_call} 0x000000000254fb94: hlt 0x000000000254fb95: hlt 0x000000000254fb96: hlt 0x000000000254fb97: hlt 

可以清楚地看到循环,与0x7fffffff进行比较,并跳转回inc 。 与此相反,在运行MAX_VALUE-1的情况下,程序集为:

 Decoding compiled method 0x000000000254f650: Code: [Entry Point] [Verified Entry Point] [Constants] # {method} &apos;runWithMaxValueMinusOne&apos; &apos;()V&apos; in &apos;MissedLoopOptimization&apos; # [sp+0x20] (sp of caller) 0x000000000254f780: sub $0x18,%rsp 0x000000000254f787: mov %rbp,0x10(%rsp) ;*synchronization entry ; - MissedLoopOptimization::runWithMaxValueMinusOne@-1 (line 36) 0x000000000254f78c: add $0x10,%rsp 0x000000000254f790: pop %rbp 0x000000000254f791: test %eax,-0x241f797(%rip) # 0x0000000000130000 ; {poll_return} 0x000000000254f797: retq 0x000000000254f798: hlt 0x000000000254f799: hlt 0x000000000254f79a: hlt 0x000000000254f79b: hlt 0x000000000254f79c: hlt 0x000000000254f79d: hlt 0x000000000254f79e: hlt 0x000000000254f79f: hlt [Exception Handler] [Stub Code] 0x000000000254f7a0: jmpq 0x000000000254e820 ; {no_reloc} [Deopt Handler Code] 0x000000000254f7a5: callq 0x000000000254f7aa 0x000000000254f7aa: subq $0x5,(%rsp) 0x000000000254f7af: jmpq 0x0000000002528d00 ; {runtime_call} 0x000000000254f7b4: hlt 0x000000000254f7b5: hlt 0x000000000254f7b6: hlt 0x000000000254f7b7: hlt 

所以我的问题是:什么是非常特殊的Integer.MAX_VALUE ,防止JIT优化它以同样的方式为Integer.MAX_VALUE-1 ? 我的猜测是与用于有符号算术的cmp指令有关,但这本身并不是一个令人信服的理由。 任何人都可以解释这一点,甚至可以给这个案件的OpenJDK HotSpot代码指针?

(另外:我希望答案也能解释在另一个问题中要求的i++++i之间的不同行为,假设缺less优化的原因实际上是由Integer.MAX_VALUE循环引起的限制)

我还没有挖掘出Java语言规范,但是我猜想它与这个区别有关:

  • i++ < (Integer.MAX_VALUE - 1)永远不会溢出。 一旦i达到Integer.MAX_VALUE - 1它增加到Integer.MAX_VALUE ,然后循环终止。

  • i++ < Integer.MAX_VALUE包含整数溢出。 一旦i达到Integer.MAX_VALUE ,它会增加一个溢出, 然后循环终止。

我假设JIT编译器是“不情愿”优化循环与这样的angular落条件 – 在整数溢出条件下有一大堆bug优化循环优化,所以不情愿可能是相当保证。

也许有一些硬性的要求,不允许整数溢出优化,尽pipe我怀疑,因为整数溢出不是直接检测或在Java中处理。