JIT没有优化涉及Integer.MAX_VALUE的循环

Mar*_*o13 49 java jit jvm-hotspot compiler-optimization

在写另一个问题的答案时,我注意到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) {}
    }
}
Run Code Online (Sandbox Code Playgroud)

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

在Win7/64上使用JDK 1.7.0_21和.执行此操作时

java -server MissedLoopOptimization
Run Code Online (Sandbox Code Playgroud)

时间结果如下:

...
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
Run Code Online (Sandbox Code Playgroud)

显然,对于这种情况MAX_VALUE-1,JIT做了人们可以期待的事情:它检测到循环是无用的,并完全消除它.但是,它在运行时不会删除循环MAX_VALUE.

开始时查看JIT组件输出可以确认这一观察结果

java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly MissedLoopOptimization
Run Code Online (Sandbox Code Playgroud)

该日志包含以下运行方法的程序集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    
Run Code Online (Sandbox Code Playgroud)

人们可以清楚地看到循环,与之比较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    
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:有什么特别之处Integer.MAX_VALUE阻止JIT以与它相同的方式优化它Integer.MAX_VALUE-1?我的猜测是与cmp指令有关,这是用于签名算术的,但仅此并不是一个令人信服的理由.任何人都可以解释这一点,甚至可能会给出一个指向这个案例的OpenJDK HotSpot代码的指针吗?

(旁白:我希望答案还将解释之间的不同的行为i++,并++i认为在其他问题问,假设失踪优化的原因是(显然)实际上是由引起Integer.MAX_VALUE循环限制)

thk*_*ala 37

我没有挖出Java语言规范,但我猜它与这种差异有关:

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

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

我假设JIT编译器"不愿意"在这种极端条件下优化输出循环 - 在整数溢出条件下有一大堆错误和循环优化,因此可能非常需要不情愿.

可能还有一些硬性要求不允许整数溢出被优化,尽管我有点怀疑,因为整数溢出不能直接检测或以其他方式在Java中处理.

  • 即使循环没有做任何事情,优化器也必须小心删除,因为它必须证明循环在消除之前不是无限的.当循环条件中涉及的变量溢出时,这显然要困难得多. (9认同)