Як уже зазначалося, компілятор JIT (щойно встигає) може оптимізувати порожній цикл, щоб видалити непотрібні ітерації. Але як?
Насправді є два компілятори JIT: C1 & C2 . Спочатку код складається з C1. C1 збирає статистику та допомагає JVM виявити, що в 100% випадків наш порожній цикл нічого не змінює і є марним. У цій ситуації С2 виходить на сцену. Коли код викликається дуже часто, його можна оптимізувати та компілювати за допомогою C2 за допомогою зібраної статистики.
Як приклад, я протестую наступний фрагмент коду (мій JDK встановлений для 9-внутрішнього уповільнення помилок ):
public class Demo {
private static void run() {
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
}
System.out.println("Done!");
}
}
За допомогою наступних параметрів командного рядка:
-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Demo.run
І існують різні версії мого методу запуску , складені відповідним чином за допомогою C1 та C2. Для мене кінцевий варіант (С2) виглядає приблизно так:
...
; B1: # B3 B2 <- BLOCK HEAD IS JUNK Freq: 1
0x00000000125461b0: mov dword ptr [rsp+0ffffffffffff7000h], eax
0x00000000125461b7: push rbp
0x00000000125461b8: sub rsp, 40h
0x00000000125461bc: mov ebp, dword ptr [rdx]
0x00000000125461be: mov rcx, rdx
0x00000000125461c1: mov r10, 57fbc220h
0x00000000125461cb: call indirect r10 ; *iload_1
0x00000000125461ce: cmp ebp, 7fffffffh ; 7fffffff => 2147483647
0x00000000125461d4: jnl 125461dbh ; jump if not less
; B2: # B3 <- B1 Freq: 0.999999
0x00000000125461d6: mov ebp, 7fffffffh ; *if_icmpge
; B3: # N44 <- B1 B2 Freq: 1
0x00000000125461db: mov edx, 0ffffff5dh
0x0000000012837d60: nop
0x0000000012837d61: nop
0x0000000012837d62: nop
0x0000000012837d63: call 0ae86fa0h
...
Це трохи безладно, але якщо придивитись уважніше, ви можете помітити, що тут довгий цикл не працює. Існує 3 блоки: B1, B2 і B3, і кроки виконання можуть бути B1 -> B2 -> B3
або B1 -> B3
. Де Freq: 1
- нормалізована передбачувана частота виконання блоку.
javap -v
щоб побачити.