Comprendre les performances des boucles dans JVM

Je joue avec jmh et dans la section sur la boucle, ils ont dit que

Vous remarquerez peut-être que plus le nombre de répétitions est élevé, plus le coût “perçu” de l’opération mesurée est faible. Jusqu’au point où nous faisons chaque addition avec 1/20 ns, bien au-delà de ce que le matériel peut réellement faire. Cela se produit parce que la boucle est fortement déroulée / en pipeline et que l’opération à mesurer est extraite de la boucle . Moral: n’abusez pas des boucles, faites confiance à JMH pour obtenir les bonnes mesures.

J’ai essayé moi-même

  @Benchmark @OperationsPerInvocation(1) public int measurewrong_1() { return reps(1); } @Benchmark @OperationsPerInvocation(1000) public int measurewrong_1000() { return reps(1000); } 

et obtenu le résultat suivant:

 Benchmark Mode Cnt Score Error Units MyBenchmark.measurewrong_1 avgt 15 2.425 ± 0.137 ns/op MyBenchmark.measurewrong_1000 avgt 15 0.036 ± 0.001 ns/op 

Cela montre en effet que MyBenchmark.measurewrong_1000 est considérablement plus rapide que MyBenchmark.measurewrong_1 . Mais je ne comprends pas vraiment l’optimisation que JVM réalise pour améliorer les performances.

Qu’est-ce qu’ils veulent dire, la boucle est déroulée / en pipeline ?

Le déroulement en boucle rend le pipeline possible. Ainsi, la CPU utilisable par pipeline (par exemple, RISC) peut exécuter le code déroulé en parallèle.

Donc si votre CPU est capable d’exécuter 5 pipelines en parallèle, votre boucle sera déroulée de la manière suivante:

 // pseudo code int pipelines = 5; for(int i = 0; i < length; i += pipelines){ s += (x + y); s += (x + y); s += (x + y); s += (x + y); s += (x + y); } 

Pipeline Risc

IF = Récupération d'instruction, ID = Décodage d'instruction, EX = Exécuter, MEM = Accès à la mémoire, WB = Enregistrer un retour

Extrait du livre blanc Oracle :

... une optimisation de compilateur standard permettant une exécution plus rapide des boucles. Le déroulement de la boucle augmente la taille du corps de la boucle tout en réduisant simultanément le nombre d'itérations. Le déroulement en boucle augmente également l'efficacité d'autres optimisations.

plus d'informations sur le pipeline: pipeline RISC classique

Loop Unrolling est une technique permettant d’aplatir plusieurs itérations de boucle en répétant le corps de la boucle.
Par exemple, dans l’exemple donné

  for (int i = 0; i < reps; i++) { s += (x + y); } 

peut être déroulé par le compilateur JIT à quelque chose comme

  for (int i = 0; i < reps - 15; i += 16) { s += (x + y); s += (x + y); // ... 16 times ... s += (x + y); } 

Ensuite, le corps de la boucle étendue peut être optimisé pour

  for (int i = 0; i < reps - 15; i += 16) { s += 16 * (x + y); } 

Évidemment, l'informatique 16 * (x + y) est beaucoup plus rapide que l'informatique (x + y) 16 fois.

Boucle Pipelining = Logiciel Pipelining.

En gros, il s’agit d’une technique utilisée pour optimiser l’efficacité des itérations de boucle séquentielles , en exécutant certaines des instructions du corps de la boucle – en parallèle .

Bien sûr, cela ne peut être fait que lorsque certaines conditions sont remplies, telles que chaque itération ne dépendant pas d’une autre, etc.

De insidehpc.com:

Le traitement en pipeline de logiciels, qui n’a en réalité rien à voir avec le traitement en pipeline de matériel, est une technique d’optimisation de boucle permettant de générer des déclarations indépendantes d’une itération. L’objective est de supprimer les dépendances afin que des instructions apparemment séquentielles puissent être exécutées en parallèle.

Voir plus ici:

  • Pipelining logiciel expliqué

  • Pipelining logiciel – Wikipedia