web-dev-qa-db-fra.com

Pourquoi Java est-il plus rapide lors de l'utilisation d'un JIT par rapport à la compilation en code machine?

J'ai entendu dire que Java devait utiliser un JIT pour être rapide. Cela est parfaitement logique lors de la comparaison avec une interprétation, mais pourquoi quelqu'un ne pourrait-il pas créer un compilateur anticipant générant du code Java rapide? Je connais gcj, mais je ne pense pas que sa sortie est généralement plus rapide que Hotspot, par exemple.

Y a-t-il des choses dans la langue qui rendent cela difficile? Je pense que cela se résume à ces choses:

  • Réflexion
  • Classloading

Qu'est-ce que je rate? Si j'évite ces fonctionnalités, serait-il possible de compiler le code Java une fois en code machine natif et de le faire?

48
Adam Goode

Le vrai tueur pour tout compilateur AOT est:

Class.forName(...)

Cela signifie que vous ne pouvez pas écrire de compilateur AOT couvrantTOUSprogrammes Java car des informations sur les caractéristiques du programme sont disponibles uniquement à l'exécution. Vous pouvez toutefois le faire sur un sous-ensemble de Java, ce que je pense que gcj fait.

Un autre exemple typique est la capacité d’un JIT à intégrer des méthodes telles que getX () directement dans les méthodes d’appel, s’il s’avère qu’il est sécuritaire de le faire, et à annuler si cela est approprié, même si le programmeur ne l’aide pas explicitement. une méthode est définitive. Le JIT peut voir que, dans le programme en cours d'exécution, une méthode donnée n'est pas remplacée et peut donc être traitée comme finale. Cela pourrait être différent lors de la prochaine invocation.

22

Un compilateur JIT peut être plus rapide car le code machine est généré sur la machine exacte sur laquelle il sera également exécuté. Cela signifie que le JIT dispose des meilleures informations possibles pour émettre un code optimisé.

Si vous pré-compilez du bytecode en code machine, le compilateur ne peut pas optimiser pour la ou les machines cibles, mais uniquement pour la machine de construction.

36
Andrew Hare

Je vais coller une réponse intéressante donnée par le James Gosling dans le livre Masterminds of Programming .

Eh bien, j’ai entendu dire que Vous avez effectivement deux compilateurs dans Dans le monde Java. Vous avez le compilateur En bytecode Java, puis vous avez Votre JIT, qui recompile fondamentalement tout à nouveau. Toutes vos optimisations effrayantes se trouvent dans le JIT .

James: Exactement. De nos jours, nous battons les très bons compilateurs C et C++ Presque toujours. Lorsque vous accédez au compilateur dynamique, vous bénéficiez de Deux avantages lorsque Du compilateur fonctionne au dernier moment. L’un des Est que vous savez exactement sur quel jeu de puces Vous utilisez. Trop souvent, lorsque Compilent un morceau de code C , Ils doivent le compiler pour exécuter Sur une architecture de type x86 Générique. Presque aucun des fichiers binaires Que vous obtenez n'est particulièrement bien réglé pour . Vous téléchargez La dernière version de Mozilla et il fonctionnera sur à peu près n’importe quel processeur à architecture Intel . Il existe à peu près Un binaire Linux. C’est plutôt générique, Et compilé avec GCC, qui est N’est pas un très bon compilateur C.

Lorsque HotSpot est exécuté, il sait exactement Sur quel chipset vous utilisez. Il Sait exactement comment fonctionne le cache. Il Sait exactement comment fonctionne la hiérarchie de la mémoire . Il sait exactement comment tous les Verrouillages de pipeline fonctionnent dans la CPU. Il sait quel jeu d'instructions A ce que cette puce a d'extensions. Il Optimise pour quelle machine Vous êtes. Ensuite, l’autre moitié Est qu’elle voit en réalité l’application En cours d’exécution. Il est possible D’avoir des statistiques qui permettent de savoir quelles choses Sont importantes. Il est capable de En ligne des choses qu’un compilateur C ne pourrait jamais faire. Le genre de choses qui deviennent En ligne dans le monde de Java est assez Incroyable. Ensuite, vous vous en tenez à la façon dont la gestion du stockage fonctionne avec Avec les éboueurs modernes. Avec un Récupérateur de déchets moderne, l’allocation de stockage Est extrêmement rapide.

Masterminds of Programming

28
Edwin Dalorzo

Le compilateur JIT de Java est également paresseux et adaptatif.

Paresseux

Étant paresseux, il ne compile que les méthodes quand il les reçoit au lieu de compiler le programme complet (très utile si vous n’utilisez pas une partie d’un programme). Le chargement de classes contribue en fait à accélérer le JIT en lui permettant d’ignorer des classes qu’il n’a pas encore rencontrées.

Adaptatif

Étant adaptatif, il commence par émettre une version rapide et erronée du code machine, puis ne fait que revenir en arrière et effectue un travail approfondi si cette méthode est utilisée fréquemment.

22
Luke Quinane

En fin de compte, cela revient au fait qu'avoir plus d'informations permet de meilleures optimisations. Dans ce cas, le JIT contient plus d'informations sur la machine sur laquelle le code est exécuté (comme l'a mentionné Andrew) et contient également de nombreuses informations d'exécution non disponibles lors de la compilation.

11
Tal Pressman

La capacité de Java à se conformer aux limites des méthodes virtuelles et à déployer efficacement les interfaces nécessite une analyse à l'exécution avant la compilation. En d'autres termes, elle nécessite un JIT. Puisque toutes les méthodes sont virtuelles et que les interfaces sont utilisées "partout", cela fait une grande différence.

6
Sam Harwell

En théorie, un compilateur JIT a un avantage sur AOT s'il dispose de suffisamment de temps et de ressources de calcul . Par exemple, si vous avez une application d'entreprise qui s'exécute pendant des jours et des mois sur un serveur multiprocesseur avec beaucoup de RAM, le compilateur JIT peut produit un meilleur code que tout compilateur AOT.

Maintenant, si vous avez une application de bureau, des éléments tels que le démarrage rapide et le temps de réponse initial (où AOT brille) deviennent plus importants. De plus, l'ordinateur risque de ne pas disposer de ressources suffisantes pour les optimisations les plus avancées.

Et si vous avez un système intégré avec des ressources rares, JIT n'a aucune chance contre AOT.

Cependant, ce qui précède n’était que théorie. En pratique, créer un compilateur JIT aussi avancé est bien plus compliqué qu’un AOT décent. Qu'en est-il des preuves pratiques?

5
Dmitry Leskov

Les JIT peuvent identifier et éliminer certaines conditions qui ne peuvent être connues qu'au moment de l'exécution. Un bon exemple est l’élimination des appels virtuels utilisés par les machines virtuelles modernes. Par exemple, lorsque la machine virtuelle trouve une instruction invokevirtual ou invokeinterface, si une seule classe remplaçant la méthode invoquée a été chargée, la VM peut réellement effectuer cette opération virtuelle. appelez static et est donc capable de l’aligner. En revanche, pour un programme C, un pointeur de fonction est toujours un pointeur de fonction et son appel ne peut pas être en ligne (dans le cas général, de toute façon).

Voici une situation où la machine virtuelle Java est en mesure d’inscrire un appel virtuel:

interface I { 
    I INSTANCE = Boolean.getBoolean("someCondition")? new A() : new B();
    void doIt(); 
}
class A implements I { 
    void doIt(){ ... } 
}
class B implements I { 
    void doIt(){ ... } 
}
// later...
I.INSTANCE.doIt();

En supposant que nous ne créions pas d'instances A ou B ailleurs et que someCondition est défini sur true, la machine virtuelle Java sait que l'appel à doIt() signifie toujours A.doIt et peut donc éviter la recherche dans la table de méthode, puis en ligne. Une construction similaire dans un environnement non JITted ne serait pas insérable.

5
gustafc

Je pense que le fait que le compilateur officiel Java soit un compilateur JIT en est une grande partie. Combien de temps a été consacré à l’optimisation de la machine virtuelle Java par rapport à un compilateur de code machine pour Java?

2
Brendan Long

Dimitry Leskov est absolument juste ici.

Tout ce qui précède n’est que la théorie de ce qui pourrait rendre JIT plus rapide, il est presque impossible de mettre en œuvre chaque scénario. En outre, étant donné que nous ne disposons que de quelques jeux d'instructions différents sur les CPU x86_64, il y a très peu à gagner à cibler chaque jeu d'instructions sur le CPU actuel. Je respecte toujours la règle du ciblage x86_64 et SSE4.2 lors de la création d'applications critiques de performance en code natif. La structure fondamentale de Java est à l'origine de nombreuses limitations, JNI peut vous aider à montrer à quel point c'est inefficace, JIT ne fait que masquer cela en accélérant globalement les choses. Outre le fait que chaque fonction est par défaut virtuelle, elle utilise également des types de classe au moment de l'exécution, par exemple en C++. C++ présente ici un avantage considérable en termes de performances, car aucun objet de classe n’a besoin d’être chargé au moment de l’exécution, il s’agit de blocs de données alloués en mémoire et initialisés uniquement sur demande. En d'autres termes, C++ n'a pas de types de classe au moment de l'exécution. Les classes Java sont des objets réels, pas seulement des modèles. Je ne vais pas entrer dans GC parce que ce n'est pas pertinent. Les chaînes Java sont également plus lentes car elles utilisent le regroupement dynamique de chaînes, ce qui obligerait l'exécution à effectuer des recherches de chaînes dans la table de pool à chaque fois. Beaucoup de ces choses sont dues au fait que Java n'a pas été conçu pour être rapide, son principe fondamental sera donc toujours lent. La plupart des langues natives (principalement le C/C++) ont été spécifiquement conçues pour être maigres et moyennes, sans gaspillage de mémoire ni de ressources. En fait, les premières versions de Java étaient terriblement lentes et fastidieuses en mémoire, avec beaucoup de métadonnées inutiles pour les variables et tout le reste. Comme c'est le cas aujourd'hui, le fait que JIT soit capable de produire un code plus rapide que les langages AOT restera une théorie.

Pensez à tout le travail que le JIT doit suivre pour faire le JIT paresseux, incrémenter un compteur chaque fois qu'une fonction est appelée, vérifier le nombre de fois qu'elle a été appelée, etc., etc. Exécuter le JIT prend beaucoup de temps. Le commerce à mes yeux n'en vaut pas la peine. C'est juste sur PC

Avez-vous déjà essayé d’exécuter Java sur Raspberry et d’autres appareils intégrés? Performance absolument terrible. JavaFX sur Raspberry? Pas même fonctionnel ... Java et son EJI sont très loin de répondre à toutes ses annonces et à la théorie que les gens lui avouent aveuglément.

0
Christopher Bekesi