Premièrement, la plupart des machines virtuelles incluent un compilateur, le "code octet interprété" est en fait assez rare (du moins dans le code de référence - ce n'est pas aussi rare dans la réalité, où votre code est généralement plus que quelques boucles triviales qui se répètent extrêmement souvent ).
Deuxièmement, bon nombre de points de repère impliqués semblent assez biaisés (que ce soit par intention ou par incompétence, je ne peux pas vraiment dire). Par exemple, il y a des années, j'ai examiné une partie du code source lié à l'un des liens que vous avez publiés. Il y avait un code comme ceci:
init0 = (int*)calloc(max_x,sizeof(int));
init1 = (int*)calloc(max_x,sizeof(int));
init2 = (int*)calloc(max_x,sizeof(int));
for (x=0; x<max_x; x++) {
init2[x] = 0;
init1[x] = 0;
init0[x] = 0;
}
Puisque calloc
fournit une mémoire qui a déjà été mise à zéro, utiliser à nouveau la for
boucle est évidemment inutile. Cela a été suivi (si la mémoire le permet) par le remplissage de la mémoire avec d’autres données (sans aucune dépendance quant à leur remise à zéro), de sorte que toute la remise à zéro était totalement inutile de toute façon. Remplacer le code ci-dessus par un simple malloc
(comme toute personne saine d'esprit l'aurait utilisée au départ) améliorait suffisamment la vitesse de la version C ++ pour battre la version Java (avec une marge assez large, si la mémoire le permet).
Prenons (pour un autre exemple) le methcall
repère utilisé dans l'entrée de blog de votre dernier lien. Malgré son nom (et son apparence possible), la version C ++ de cet outil ne mesure pas grand-chose du tout sur le temps système d'appel de méthode. La partie du code qui s'avère critique est dans la classe Toggle:
class Toggle {
public:
Toggle(bool start_state) : state(start_state) { }
virtual ~Toggle() { }
bool value() {
return(state);
}
virtual Toggle& activate() {
state = !state;
return(*this);
}
bool state;
};
La partie critique s'avère être le state = !state;
. Considérez ce qui se passe lorsque nous modifions le code pour coder l'état int
de la manière suivante bool
:
class Toggle {
enum names{ bfalse = -1, btrue = 1};
const static names values[2];
int state;
public:
Toggle(bool start_state) : state(values[start_state])
{ }
virtual ~Toggle() { }
bool value() { return state==btrue; }
virtual Toggle& activate() {
state = -state;
return(*this);
}
};
Ce changement mineur améliore la vitesse globale d'environ 5: 1 . Bien que l' objectif ait été conçu pour mesurer le temps d'appel d'une méthode, en réalité, il s'agissait essentiellement du temps nécessaire pour effectuer la conversion entre int
et bool
. Je conviens certainement que l'inefficacité montrée par l'original est regrettable - mais étant donné que cela semble rarement apparaître dans un code réel, et qu'il est facile de le réparer quand / si cela se produit, j'ai du mal à penser de cela comme signifiant beaucoup.
Au cas où quelqu'un déciderait de réexécuter les tests de performance impliqués, je devrais également ajouter qu'il y a une modification presque aussi triviale à la version Java qui produit (ou au moins une fois produite - je n'ai pas réexécuté les tests avec une JVM récente pour confirmer encore) une amélioration assez substantielle de la version Java également. La version Java a un NthToggle :: activate () qui ressemble à ceci:
public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
this.state = !this.state;
this.counter = 0;
}
return(this);
}
Changer cela pour appeler la fonction de base au lieu de manipuler this.state
directement donne une amélioration assez rapide de la vitesse (mais pas assez pour suivre la version modifiée de C ++).
Nous nous retrouvons donc avec une fausse hypothèse sur les codes d’octets interprétés par rapport à certains des pires points de repère (que j’ai jamais vus). Ni donne un résultat significatif.
Selon ma propre expérience, avec des programmeurs tout aussi expérimentés qui accordent autant d’attention à l’optimisation, C ++ battra Java le plus souvent - mais (au moins entre ces deux), le langage fera rarement autant de différence que les programmeurs et le concepteur. Les repères cités nous en disent plus sur l’inaptitude / la (mauvaise) honnêteté de leurs auteurs que sur les langues qu’ils prétendent comparer.
[Edit: Comme suggéré dans un endroit ci-dessus mais jamais indiqué aussi directement que je devrais probablement avoir, les résultats que je cite sont ceux que j’ai obtenus lorsque j’ai testé cela il y a environ 5 ans, en utilisant des implémentations C ++ et Java qui étaient courantes à cette époque. . Je n'ai pas relancé les tests avec les implémentations actuelles. Un coup d'œil, cependant, indique que le code n'a pas été corrigé, donc tout ce qui aurait changé serait la capacité du compilateur à dissimuler les problèmes dans le code.]
Cependant, si nous ignorons les exemples Java, il est en fait possible que le code interprété s'exécute plus rapidement que le code compilé (bien que difficile et quelque peu inhabituel).
Cela se produit généralement de la manière suivante: le code en cours d'interprétation est beaucoup plus compact que le code de la machine ou s'exécute sur un processeur doté d'un cache de données plus important que le cache de code.
Dans un tel cas, un petit interprète (par exemple, l'interprète interne d'une implémentation Forth) peut être entièrement inséré dans le cache de code et le programme qu'il interprète s'inscrit entièrement dans le cache de données. Le cache est généralement plus rapide que la mémoire principale d'au moins 10 fois, et souvent beaucoup plus (un facteur de 100 n'est plus particulièrement rare).
Donc, si le cache est plus rapide que la mémoire principale d'un facteur N et qu'il faut moins de N instructions de code machine pour implémenter chaque code d'octet, le code d'octet devrait gagner (je simplifie, mais je pense que l'idée générale devrait tout de même être apparent).