Grande différence de vitesse des méthodes statiques et non statiques équivalentes

Dans ce code, lorsque je crée un object dans la méthode main et que j’appelle ensuite cette méthode: ff.twentyDivCount(i) (il tourne en 16010 ms), il s’exécute beaucoup plus rapidement en appelant cette annotation: twentyDivCount(i) (s’exécute dans 59516 ms). Bien sûr, quand je le lance sans créer d’object, je rend la méthode statique, de sorte qu’elle peut être appelée dans la main.

 public class ProblemFive { // Counts the number of numbers that the entry is evenly divisible by, as max is 20 int twentyDivCount(int a) { // Change to static int.... when using it directly int count = 0; for (int i = 1; i 0; i--) { int temp = ff.twentyDivCount(i); // Faster way // twentyDivCount(i) - slower if (temp == 20) { result = i; System.out.println(result); } } System.out.println(result); long end = System.currentTimeMillis();; System.out.println((end - startT) + " ms"); } } 

EDIT: Jusqu’à présent, il semble que différentes machines produisent des résultats différents, mais l’utilisation de JRE 1.8. * Est l’endroit où le résultat original semble être reproduit de manière cohérente.

En utilisant JRE 1.8.0_45, j’obtiens des résultats similaires.

Enquête:

  1. java exécutant avec le -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining options de VM -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining montre que les deux méthodes sont compilées et en ligne
  2. Regarder l’assemblage généré pour les méthodes elles-mêmes ne montre aucune différence significative
  3. Une fois qu’ils sont intégrés, l’assemblage généré dans main est très différent, la méthode d’instance étant optimisée de manière plus agressive, notamment en termes de déroulement de la boucle.

J’ai ensuite effectué votre test à nouveau, mais avec différents parameters de déroulement de la boucle pour confirmer les soupçons ci-dessus. J’ai couru votre code avec:

  • -XX:LoopUnrollLimit=0 et les deux méthodes s’exécutent lentement (similaire à la méthode statique avec les options par défaut).
  • -XX:LoopUnrollLimit=100 et les deux méthodes s’exécutent rapidement (similaire à la méthode d’instance avec les options par défaut).

En conclusion, il semble qu’avec les parameters par défaut, le JIT de hotspot 1.8.0_45 ne soit pas capable de dérouler la boucle lorsque la méthode est statique (bien que je ne sache pas pourquoi elle se comporte de la sorte). D’autres machines virtuelles peuvent donner des résultats différents.

Juste une hypothèse non prouvée basée sur une réponse d’assylias.

La JVM utilise un seuil pour le déroulement de la boucle, qui est quelque chose comme 70. Pour une raison quelconque, l’appel statique est légèrement plus gros et ne se déroule pas.

Mise à jour des résultats

  • Avec le LoopUnrollLimit dans les 52 ci-dessous, les deux versions sont lentes.
  • Entre 52 et 71, seule la version statique est lente.
  • Au-dessus de 71, les deux versions sont rapides.

C’est étrange car je suppose que l’appel statique est juste un peu plus gros dans la représentation interne et que l’OP frappe un cas étrange. Mais la différence semble être d’environ 20, ce qui n’a aucun sens.

 -XX:LoopUnrollLimit=51 5400 ms NON_STATIC 5310 ms STATIC -XX:LoopUnrollLimit=52 1456 ms NON_STATIC 5305 ms STATIC -XX:LoopUnrollLimit=71 1459 ms NON_STATIC 5309 ms STATIC -XX:LoopUnrollLimit=72 1457 ms NON_STATIC 1488 ms STATIC 

Pour ceux qui souhaitent expérimenter, ma version peut être utile.

Lorsque ceci est exécuté en mode débogage, les nombres sont les mêmes pour l’instance et les cas statiques. Cela signifie en outre que le JIT hésite à comstackr le code en code natif dans le cas statique de la même manière que dans le cas de la méthode d’instance.

Pourquoi le fait-il? C’est dur à dire; cela ferait probablement la bonne chose si c’était une application plus grande …

Je viens de modifier légèrement le test et j’ai obtenu les résultats suivants:

Sortie:

 Dynamic Test: 465585120 232792560 232792560 51350 ms Static Test: 465585120 232792560 232792560 52062 ms 

REMARQUE

Pendant que je les testais séparément, j’ai obtenu environ 52 secondes pour la dynamic et environ 200 secondes pour la statique.

C’est le programme:

 public class ProblemFive { // Counts the number of numbers that the entry is evenly divisible by, as max is 20 int twentyDivCount(int a) { // Change to static int.... when using it directly int count = 0; for (int i = 1; i<21; i++) { if (a % i == 0) { count++; } } return count; } static int twentyDivCount2(int a) { int count = 0; for (int i = 1; i<21; i++) { if (a % i == 0) { count++; } } return count; } public static void main(String[] args) { System.out.println("Dynamic Test: " ); dynamicTest(); System.out.println("Static Test: " ); staticTest(); } private static void staticTest() { long startT = System.currentTimeMillis();; int start = 500000000; int result = start; for (int i = start; i > 0; i--) { int temp = twentyDivCount2(i); if (temp == 20) { result = i; System.out.println(result); } } System.out.println(result); long end = System.currentTimeMillis();; System.out.println((end - startT) + " ms"); } private static void dynamicTest() { long startT = System.currentTimeMillis();; int start = 500000000; int result = start; ProblemFive ff = new ProblemFive(); for (int i = start; i > 0; i--) { int temp = ff.twentyDivCount(i); // Faster way if (temp == 20) { result = i; System.out.println(result); } } System.out.println(result); long end = System.currentTimeMillis();; System.out.println((end - startT) + " ms"); } } 

J’ai également changé l’ordre du test pour:

 public static void main(Ssortingng[] args) { System.out.println("Static Test: " ); staticTest(); System.out.println("Dynamic Test: " ); dynamicTest(); } 

Et j’ai ceci:

 Static Test: 465585120 232792560 232792560 188945 ms Dynamic Test: 465585120 232792560 232792560 50106 ms 

Comme vous le voyez, si la dynamic est appelée avant statique, la vitesse pour la statique a considérablement diminué.

Basé sur ce benchmark:

Je suppose que tout dépend de l’optimisation de la JVM. Je vous recommande donc simplement de suivre la règle de base pour utiliser des méthodes statiques et dynamics.

RÈGLE DE POUCE:

Java: quand utiliser des méthodes statiques

S’il vous plaît essayez:

 public class ProblemFive { public static ProblemFive PROBLEM_FIVE = new ProblemFive(); public static void main(Ssortingng[] args) { long startT = System.currentTimeMillis(); int start = 500000000; int result = start; for (int i = start; i > 0; i--) { int temp = PROBLEM_FIVE.twentyDivCount(i); // faster way // twentyDivCount(i) - slower if (temp == 20) { result = i; System.out.println(result); System.out.println((System.currentTimeMillis() - startT) + " ms"); } } System.out.println(result); long end = System.currentTimeMillis(); System.out.println((end - startT) + " ms"); } int twentyDivCount(int a) { // change to static int.... when using it directly int count = 0; for (int i = 1; i < 21; i++) { if (a % i == 0) { count++; } } return count; } }