[英]JIT not optimizing loop that involves 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} '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
指令有關, 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
,它會加1,導致溢出, 然后循環終止。
我假設JIT編譯器“不願意”在這種極端條件下優化輸出循環 - 在整數溢出條件下有一大堆錯誤和循環優化,因此可能非常需要不情願。
可能還有一些硬性要求不允許對整數溢出進行優化,盡管我有點懷疑,因為整數溢出不能直接檢測到或以其他方式在Java中處理。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.