Comme d'autres l'ont souligné, le test est imparfait à bien des égards.
Vous ne nous avez pas dit exactement comment vous avez fait ce test. Cependant, j'ai essayé de mettre en œuvre un test "naïf" (sans vouloir vous offenser) comme celui-ci :
class PrePostIncrement
{
public static void main(String args[])
{
for (int j=0; j<3; j++)
{
for (int i=0; i<5; i++)
{
long before = System.nanoTime();
runPreIncrement();
long after = System.nanoTime();
System.out.println("pre : "+(after-before)/1e6);
}
for (int i=0; i<5; i++)
{
long before = System.nanoTime();
runPostIncrement();
long after = System.nanoTime();
System.out.println("post : "+(after-before)/1e6);
}
}
}
private static void runPreIncrement()
{
final int n = Integer.MAX_VALUE;
int i = 0;
while (++i < n) {}
}
private static void runPostIncrement()
{
final int n = Integer.MAX_VALUE;
int i = 0;
while (i++ < n) {}
}
}
Lorsqu'on l'exécute avec les paramètres par défaut, il semble y avoir une petite différence. Mais le réel Le défaut du benchmark devient évident lorsque vous l'exécutez avec l'option -server
drapeau. Dans mon cas, les résultats ressemblent alors à quelque chose comme
...
pre : 6.96E-4
pre : 6.96E-4
pre : 0.001044
pre : 3.48E-4
pre : 3.48E-4
post : 1279.734543
post : 1295.989086
post : 1284.654267
post : 1282.349093
post : 1275.204583
Évidemment, la version pré-incrémentée a été complètement optimisé . La raison en est assez simple : Le résultat n'est pas utilisé. Le fait que la boucle soit exécutée ou non n'a aucune importance, aussi le JIT le supprime simplement.
Ceci est confirmé par un regard sur le désassemblage du hotspot : La version pré-incrémentée donne ce code :
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x0000000055060500} 'runPreIncrement' '()V' in 'PrePostIncrement'
# [sp+0x20] (sp of caller)
0x000000000286fd80: sub $0x18,%rsp
0x000000000286fd87: mov %rbp,0x10(%rsp) ;*synchronization entry
; - PrePostIncrement::runPreIncrement@-1 (line 28)
0x000000000286fd8c: add $0x10,%rsp
0x000000000286fd90: pop %rbp
0x000000000286fd91: test %eax,-0x243fd97(%rip) # 0x0000000000430000
; {poll_return}
0x000000000286fd97: retq
0x000000000286fd98: hlt
0x000000000286fd99: hlt
0x000000000286fd9a: hlt
0x000000000286fd9b: hlt
0x000000000286fd9c: hlt
0x000000000286fd9d: hlt
0x000000000286fd9e: hlt
0x000000000286fd9f: hlt
La version post-incrémentation donne ce code :
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x00000000550605b8} 'runPostIncrement' '()V' in 'PrePostIncrement'
# [sp+0x20] (sp of caller)
0x000000000286d0c0: sub $0x18,%rsp
0x000000000286d0c7: mov %rbp,0x10(%rsp) ;*synchronization entry
; - PrePostIncrement::runPostIncrement@-1 (line 35)
0x000000000286d0cc: mov $0x1,%r11d
0x000000000286d0d2: jmp 0x000000000286d0e3
0x000000000286d0d4: nopl 0x0(%rax,%rax,1)
0x000000000286d0dc: data32 data32 xchg %ax,%ax
0x000000000286d0e0: inc %r11d ; OopMap{off=35}
;*goto
; - PrePostIncrement::runPostIncrement@11 (line 36)
0x000000000286d0e3: test %eax,-0x243d0e9(%rip) # 0x0000000000430000
;*goto
; - PrePostIncrement::runPostIncrement@11 (line 36)
; {poll}
0x000000000286d0e9: cmp $0x7fffffff,%r11d
0x000000000286d0f0: jl 0x000000000286d0e0 ;*if_icmpge
; - PrePostIncrement::runPostIncrement@8 (line 36)
0x000000000286d0f2: add $0x10,%rsp
0x000000000286d0f6: pop %rbp
0x000000000286d0f7: test %eax,-0x243d0fd(%rip) # 0x0000000000430000
; {poll_return}
0x000000000286d0fd: retq
0x000000000286d0fe: hlt
0x000000000286d0ff: hlt
Je ne comprends pas très bien pourquoi il semble que ce soit le cas. no supprimer la version post-incrément. (En fait, j'envisage de poser cette question séparément). Mais au moins, cela explique pourquoi vous pourriez voir des différences avec un "ordre de grandeur"...
EDIT : Il est intéressant de noter qu'en changeant la limite supérieure de la boucle de Integer.MAX_VALUE
a Integer.MAX_VALUE-1
entonces les deux sont optimisées et ne nécessitent pas de temps. D'une manière ou d'une autre, cette limite (qui apparaît toujours comme 0x7fffffff
dans l'assemblage) empêche l'optimisation. On peut supposer que cela a quelque chose à voir avec le fait que la comparaison est mise en correspondance avec un (singé !) cmp
instruction, mais je ne peux pas donner une raison profonde au-delà de cela. Le JIT fonctionne de manière mystérieuse...