49 votes

JIT n'optimise pas la boucle impliquant Integer.MAX_VALUE

Lors de l'écriture d'une réponse à une autre question, j'ai remarqué une étrange frontière de cas pour JIT optimisation.

Le programme suivant n'est pas un "Microbenchmark" et pas destiné à mesurer de manière fiable un temps d'exécution (comme indiqué dans la réponse à l'autre question). C'est uniquement considéré comme un MCVE de reproduire le problème:

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) {}
    }
}

Essentiellement, il s'exécute de la même boucle, while (i++ < n){}, où la limite n est une fois mis à l' Integer.MAX_VALUE, et une fois à l' Integer.MAX_VALUE-1.

Lors de l'exécution de ce sur Win7/64 avec JDK 1.7.0_21 et

java -server MissedLoopOptimization

le moment les résultats sont comme suit:

...
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

Évidemment, pour le cas d' MAX_VALUE-1, l'équipe fait ce qu'on pouvait attendre: Il détecte que la boucle est inutile, et élimine complètement. Cependant, il n'est pas de supprimer la boucle quand il est en cours d'exécution jusqu'à l' MAX_VALUE.

Cette observation est confirmée par un coup d'oeil à l'équipe commune d'enquête de l'assemblée de sortie lors du démarrage avec

java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly MissedLoopOptimization

Le journal contient les éléments suivants de l'assemblée pour la méthode qui va jusqu'à MAX_VALUE:

Decoding compiled method 0x000000000254fa10:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} &apos;runWithMaxValue&apos; &apos;()V&apos; in &apos;MissedLoopOptimization&apos;
  #           [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    

On peut clairement voir la boucle, avec la comparaison d' 0x7fffffff et le saut de retour à la inc. En revanche, l'assemblée, pour le cas où il est en cours d'exécution jusqu'à l' MAX_VALUE-1:

Decoding compiled method 0x000000000254f650:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} &apos;runWithMaxValueMinusOne&apos; &apos;()V&apos; in &apos;MissedLoopOptimization&apos;
  #           [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    

Donc ma question est: Quel est si spécial à propos de l' Integer.MAX_VALUE qui empêche l'équipe de l'optimisation de la même manière comme il le fait pour d' Integer.MAX_VALUE-1? J'imagine que cela a à voir avec l' cmp instruction, qui est destiné à être signé de l'arithmétique, mais cela seul n'est pas vraiment une raison convaincante. Quelqu'un peut-il expliquer cela, et peut-être même donner un pointeur vers l'OpenJDK HotSpot code où ce cas est traité?

(Aparté: j'espère que la réponse sera également expliquer la différence de comportement entre i++ et ++i qui a été demandé dans la question, en supposant que la raison pour le manque d'optimisation est (évidemment) en fait causée par l' Integer.MAX_VALUE boucle de limite)

37voto

thkala Points 36148

Je n'ai pas creusé le Langage Java cahier des charges, mais je suppose que cela a à voir avec cette différence:

  • i++ < (Integer.MAX_VALUE - 1) jamais de débordements. Une fois i atteint Integer.MAX_VALUE - 1 il est incrémenté à la Integer.MAX_VALUE , puis la boucle se termine.

  • i++ < Integer.MAX_VALUE contient un débordement d'entier. Une fois i atteint Integer.MAX_VALUE, il est incrémenté par un causant un débordement et puis la boucle se termine.

Je suppose que le compilateur JIT "réticents" à optimiser-boucles avec un tel angle conditions - il y avait un tas de bugs w.r.t. boucle d'optimisation de dépassement d'entier, de sorte que la réticence est sans doute tout à fait justifiée.

Il peut également y être dur exigence qui ne permettent pas de débordements d'entiers pour être optimisé, bien que j'ai un peu de doute que, depuis des débordements d'entiers ne sont pas directement détectable ou autrement manipulés en Java.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X