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 的情况,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} 'runWithMaxValue' '()V' in 'MissedLoopOptimization'
# [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} 'runWithMaxValueMinusOne' '()V' in 'MissedLoopOptimization'
# [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
这是另一个问题所要求的,假设缺少优化的原因(显然) 实际上
是由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编译器“不愿意”在这种极端情况下优化循环-
在整数溢出条件下有很多bug会导致
wrt循环优化,因此可能确实需要这样做。
可能还有一些硬性要求,不允许对整数溢出进行优化,尽管我对此有些怀疑,因为整数溢出无法直接检测到或用Java处理。