JIT没有优化涉及Integer.MAX_VALUE的循环 [英] JIT not optimizing loop that involves Integer.MAX_VALUE
问题描述
在为另一个问题撰写答案时,我注意到JIT优化的一个奇怪的边界情况。
While writing an answer to another question, I noticed a strange border case for JIT optimization.
以下程序不是Microbenchmark而不旨在可靠地测量执行时间(如指出的那样)在另一个问题的答案中)。它仅用作 MCVE 来重现该问题:
The following program is not a "Microbenchmark" and not intended to reliably measure an execution time (as pointed out in the answers to the other question). It is solely intended as an MCVE to reproduce the issue:
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
。
It basically runs the same loop, while (i++ < n){}
, where the limit n
is once set to Integer.MAX_VALUE
, and once to Integer.MAX_VALUE-1
.
在Win7 / 64上使用JDK 1.7.0_21和
When executing this on Win7/64 with JDK 1.7.0_21 and
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
运行时,不会删除循环。
Obviously, for the case of MAX_VALUE-1
, the JIT does what one could expect: It detects that the loop is useless, and completely eliminates it. However, it does not remove the loop when it is running up to MAX_VALUE
.
以
java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly MissedLoopOptimization
该日志包含以下程序集,该程序集最多可运行 MAX_VALUE
:
The log contains the following assembly for the method that runs up to 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
:
One can clearly see the loop, with the comparison to 0x7fffffff
and the jump back to inc
. In contrast to that, the assembly for the case where it is running up to 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代码的指针吗?
So my question is: What is so special about the Integer.MAX_VALUE
that prevents the JIT from optimizing it in the same way as it does for Integer.MAX_VALUE-1
? My guess would be that has to do with the cmp
instruction, which is intended for signed arithmetic, but that alone is not really a convincing reason. Can anybody explain this, and maybe even give a pointer to the OpenJDK HotSpot code where this case is treated?
(旁白:我希望答案也能解释一下假设丢失的原因,在另一个问题中要求的 i ++
和 ++ i
之间的不同行为优化(显然)实际由 Integer.MAX_VALUE
循环限制引起)
(An aside: I hope that the answer will also explain the different behavior between i++
and ++i
that was asked for in the other question, assuming that the reason for the missing optimization is (obviously) actually caused by the Integer.MAX_VALUE
loop limit)
推荐答案
我没有挖出Java语言规范,但我猜它与这种差异有关:
I have not dug up the Java Language Specification, but I'd guess that it has to do with this difference:
-
i ++< (Integer.MAX_VALUE - 1)
永不溢出。一旦i
到达Integer.MAX_VALUE - 1
,它就会增加到Integer.MAX_VALUE
然后循环终止。
i++ < (Integer.MAX_VALUE - 1)
never overflows. Oncei
reachesInteger.MAX_VALUE - 1
it is incremented toInteger.MAX_VALUE
and then the loop terminates.
i ++< Integer.MAX_VALUE
包含整数溢出。一旦 i
到达 Integer.MAX_VALUE
,它就会增加1,导致溢出,然后循环终止。
i++ < Integer.MAX_VALUE
contains an integer overflow. Once i
reaches Integer.MAX_VALUE
, it is incremented by one causing an overflow and then the loop terminates.
我认为JIT编译器不情愿优化输出具有这种拐角条件的循环 - 有一个一大堆错误 wrt整数溢出条件下的循环优化,因此可能非常需要磁阻。
I assume that the JIT compiler is "reluctant" to optimize-out loops with such corner conditions - there was a whole bunch of bugs w.r.t. loop optimization in integer overflow conditions, so that reluctance is probably quite warranted.
可能还有一些硬性要求不允许对整数溢出进行优化,尽管我以某种方式怀疑,因为整数溢出不能直接检测到或以Java方式处理。
There may also be some hard requirement that does not allow integer overflows to be optimized-out, although I somehow doubt that since integer overflows are not directly detectable or otherwise handled in Java.
这篇关于JIT没有优化涉及Integer.MAX_VALUE的循环的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!