J'utilise Windows 8.1 x64 avec la mise à jour de Java 7 45 x64 (aucun Java 32 bits installé) sur une tablette Surface Pro 2.
Le code ci-dessous prend 1688 ms lorsque le type de i est long et 109 ms lorsque i est un int. Pourquoi long (un type 64 bits) est-il un ordre de grandeur plus lent que int sur une plate-forme 64 bits avec une JVM 64 bits?
Ma seule spéculation est que le processeur prend plus de temps pour ajouter un entier de 64 bits qu'un entier de 32 bits, mais cela semble peu probable. Je soupçonne que Haswell n'utilise pas d'additionneurs à effet d'entraînement.
J'exécute ceci dans Eclipse Kepler SR1, btw.
public class Main {
private static long i = Integer.MAX_VALUE;
public static void main(String[] args) {
System.out.println("Starting the loop");
long startTime = System.currentTimeMillis();
while(!decrementAndCheck()){
}
long endTime = System.currentTimeMillis();
System.out.println("Finished the loop in " + (endTime - startTime) + "ms");
}
private static boolean decrementAndCheck() {
return --i < 0;
}
}
Edit: Voici les résultats du code C ++ équivalent compilé par VS 2013 (ci-dessous), même système. long: 72265ms int: 74656ms Ces résultats étaient en mode débogage 32 bits.
En mode de libération 64 bits: longue: 875ms long long: 906ms int: 1047ms
Cela suggère que le résultat que j'ai observé est l'étrangeté de l'optimisation de la JVM plutôt que les limitations du processeur.
#include "stdafx.h"
#include "iostream"
#include "windows.h"
#include "limits.h"
long long i = INT_MAX;
using namespace std;
boolean decrementAndCheck() {
return --i < 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << "Starting the loop" << endl;
unsigned long startTime = GetTickCount64();
while (!decrementAndCheck()){
}
unsigned long endTime = GetTickCount64();
cout << "Finished the loop in " << (endTime - startTime) << "ms" << endl;
}
Edit: Je viens de réessayer dans Java 8 RTM, pas de changement significatif.
la source
currentTimeMillis()
, exécuter du code qui peut être complètement optimisé de manière triviale, etc. sentent les résultats peu fiables.long
comme compteur de boucle, car le compilateur JIT optimisait la sortie de boucle, quand j'utilisais unint
. Il faudrait regarder le désassemblage du code machine généré.Réponses:
Ma JVM fait cette chose assez simple à la boucle interne lorsque vous utilisez
long
s:Il triche, dur, quand vous utilisez
int
s; d'abord, il y a des vilains que je ne prétends pas comprendre mais qui ressemble à une configuration pour une boucle déroulée:puis la boucle déroulée elle-même:
puis le code de démontage de la boucle déroulée, elle-même un test et une boucle droite:
Cela va donc 16 fois plus vite pour les entiers car le JIT a déroulé la
int
boucle 16 fois, mais n'a pas dulong
tout déroulé la boucle .Pour être complet, voici le code que j'ai réellement essayé:
Les vidages d'assemblage ont été générés à l'aide des options
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
. Notez que vous devez manipuler votre installation JVM pour que cela fonctionne également pour vous; vous devez placer une bibliothèque partagée aléatoire exactement au bon endroit ou elle échouera.la source
long
version est plus lente, mais plutôt que laint
version est plus rapide. Ça a du sens. Probablement pas autant d'efforts ont été investis pour que le JIT optimise leslong
expressions.gcc
utilise-f
comme commutateur de ligne de commande pour "drapeau", et l'unroll-loops
optimisation est activée en disant-funroll-loops
. J'utilise simplement "dérouler" pour décrire l'optimisation.i-=16
, ce qui est bien sûr 16 fois plus rapide.La pile JVM est définie en termes de mots , dont la taille est un détail d'implémentation mais doit être d'au moins 32 bits de large. L'implémenteur JVM peut utiliser des mots de 64 bits, mais le bytecode ne peut pas s'appuyer sur cela, et les opérations avec
long
oudouble
valeurs doivent donc être traitées avec un soin particulier. En particulier, les instructions de branche entière JVM sont définies exactement sur le typeint
.Dans le cas de votre code, le démontage est instructif. Voici le bytecode de la
int
version compilée par Oracle JDK 7:Notez que la JVM chargera la valeur de votre statique
i
(0), en soustraira un (3-4), dupliquera la valeur sur la pile (5) et la repoussera dans la variable (6). Il effectue ensuite une branche de comparaison avec zéro et renvoie.La version avec le
long
est un peu plus compliquée:Premièrement, lorsque la JVM duplique la nouvelle valeur sur la pile (5), elle doit dupliquer deux mots de pile. Dans votre cas, il est fort possible que ce ne soit pas plus cher que d'en dupliquer un, car la JVM est libre d'utiliser un mot de 64 bits si cela vous convient. Cependant, vous remarquerez que la logique de branche est plus longue ici. La JVM n'a pas d'instruction pour comparer a
long
avec zéro, elle doit donc pousser une constante0L
sur la pile (9), faire unelong
comparaison générale (10), puis bifurquer sur la valeur de ce calcul.Voici deux scénarios plausibles:
long
version, en poussant et en affichant plusieurs valeurs supplémentaires, et celles-ci se trouvent sur la pile gérée virtuelle , pas sur la vraie pile de CPU assistée par matériel. Si tel est le cas, vous constaterez toujours une différence de performances significative après le préchauffage.Je vous recommande d' écrire un microbenchmark correct pour éliminer l'effet du démarrage du JIT, et d'essayer également cela avec une condition finale qui n'est pas nulle, pour forcer la JVM à faire la même comparaison
int
avec lelong
.la source
== 0
, ce qui semble être une partie disproportionnée des résultats de référence. Il me semble plus probable qu'OP essaie de mesurer une gamme plus générale d'opérations, et cette réponse souligne que le point de référence est fortement biaisé vers une seule de ces opérations.L'unité de base des données dans une machine virtuelle Java est le mot. Le choix de la bonne taille de mot est laissé lors de la mise en œuvre de la JVM. Une implémentation JVM doit choisir une taille de mot minimale de 32 bits. Il peut choisir une taille de mot plus élevée pour gagner en efficacité. Il n'y a pas non plus de restriction selon laquelle une JVM 64 bits doit choisir uniquement un mot 64 bits.
L'architecture sous-jacente ne règle pas que la taille des mots doit également être la même. JVM lit / écrit les données mot par mot. C'est la raison pour laquelle cela peut prendre plus de temps qu'un int .
Ici vous pouvez en savoir plus sur le même sujet.
la source
Je viens d'écrire un benchmark en utilisant un compas .
Les résultats sont tout à fait cohérents avec le code d'origine: une accélération de ~ 12x pour l'utilisation de
int
overlong
. Il semble certainement que le déroulement de la boucle rapporté par tmyklebu ou quelque chose de très similaire se déroule.Ceci est mon code; notez qu'il utilise un instantané nouvellement construit de
caliper
, car je ne pouvais pas comprendre comment coder par rapport à leur version bêta existante.la source
Pour mémoire, cette version fait un "échauffement" grossier:
Les temps globaux s'améliorent d'environ 30%, mais le rapport entre les deux reste à peu près le même.
la source
int
c'est 20 fois plus rapide) avec ce code.Pour les archives:
si j'utilise
(changé "l--" en "l = l - 1l") les performances longues s'améliorent d'environ 50%
la source
Je n'ai pas de machine 64 bits pour tester, mais la différence plutôt grande suggère qu'il y a plus que le bytecode légèrement plus long au travail.
Je vois des temps très proches pour long / int (4400 vs 4800ms) sur mon 1.7.0_45 32 bits.
Ce n'est qu'une supposition , mais je soupçonne fortement que c'est l'effet d'une pénalité de désalignement de la mémoire. Pour confirmer / nier la suspicion, essayez d'ajouter un public static int dummy = 0; avant la déclaration de i. Cela poussera i vers le bas de 4 octets dans la disposition de la mémoire et peut le rendre correctement aligné pour de meilleures performances.Confirmé comme ne causant pas le problème.ÉDITER:
Le raisonnement derrière cela est que la machine virtuelle ne peut pas réorganiser les champs à sa guise en ajoutant un remplissage pour un alignement optimal, car cela peut interférer avec JNI(Pas le cas).la source