45 votes

Pourquoi est-ce le code Java 6x plus rapide que l'identique de code C#?

Mise à JOUR 2:
Modification de la cible de x86 à anycpu a abaissé la moyenne des temps d'exécution à 84ms par run, en baisse à partir de 282ms. Je devrais peut-être de diviser cette de dans un deuxième thread?

Mise à JOUR:
Grâce à Femaref ci-dessous qui a souligné certains problèmes de tests, et en effet, après avoir suivi ses conseils, les temps sont plus faibles, indiquant que la VM moment de l'installation a été importante en Java, mais probablement pas en C#. En C#, il a été les symboles de débogage qui ont été importantes.

J'ai mis à jour mon code pour l'exécution de chaque boucle de 10 000 fois, et la seule sortie de la moyenne de la ms à la fin. Le seul changement que j'ai fait a été de la version C# où je suis passé à l' Chronomètre classe pour une résolution plus grande. J'ai collé avec millisecondes, car il est assez bon.

Résultats:
Les tests ne sont pas expliquer pourquoi Java est (encore) beaucoup plus rapide que celle de C#. C# performance a été meilleure, mais ce peut être entièrement expliquée en supprimant les symboles de débogage. Si vous lisez Mike Deux et j'ai du échanger sur les commentaires attachés à cette OP, vous verrez que j'ai ~280ms moyenne dans cinq séries du code C#, simplement en changeant de Débogage pour publication.

Numéros:

  • 10 000 le comte de la boucle de la non modifié code Java m'a donné une moyenne de 45ms (en bas de 55ms)
  • 10 000 le comte de la boucle de code C# en utilisant le Chronomètre de classe m'a donné une moyenne de 282ms (en bas de 320ms)

Tout cela laisse la différence inexpliquée. En effet, le différentiel de pire. Java est passé de ~5,8 x plus rapide à ~6.2 x plus vite.

(Original post ci-dessous)

J'ai quelques solutions différentes pour Projet Euler problème 5, mais le temps d'exécution de la différence entre les deux langues/plates-formes dans cette mise en oeuvre particulière m'intrigue. Je ne fais pas de l'optimisation avec les drapeaux de compilation, tout simplement javac (via la ligne de commande) et csc (via Visual Studio).

Voici le code Java. Il se termine en 55ms.

public class Problem005b
{
 public static void main(String[] args)
{
 long begin = Système.currentTimeMillis();
 int i = 20;
 while (true)
{
 si (
 (i % 19 == 0) &&
 (i % 18 == 0) &&
 (i % 17 == 0) &&
 (i % 16 == 0) &&
 (i % 15 == 0) &&
 (i % 14 == 0) &&
 (i % 13 == 0) &&
 (i % 12 == 0) &&
 (i % 11 == 0)
)
{
break;
}
 i += 20;
}
 long fin = Système.currentTimeMillis();
Système.out.println(i);
 Système.out.println(fin-début du " + "ms");
 } 
}

Voici l'identique de code C#. Il se termine en 320ms

en utilisant le Système;

espace de noms ProjectEuler05
{
 classe Problem005
{
 static void main(String[] args)
{
 DateTime commencer = DateTime.Maintenant;
 int i = 20;
 while (true)
{
 si (
 (i % 19 == 0) &&
 (i % 18 == 0) &&
 (i % 17 == 0) &&
 (i % 16 == 0) &&
 (i % 15 == 0) &&
 (i % 14 == 0) &&
 (i % 13 == 0) &&
 (i % 12 == 0) &&
 (i % 11 == 0)
)
{
break;
}
 i += 20;
}
 DateTime fin = DateTime.Maintenant;
 Laps de temps écoulé = en fin de commencer;
Console.WriteLine(i);
 Console.WriteLine(écoulé.TotalMilliseconds + "ms");
}
}
}

40voto

Femaref Points 41959
  1. Pour le temps d'exécution de code, vous devez utiliser l' StopWatch classe.
  2. Aussi, vous avez à rendre compte de l'JIT, l'exécution, etc, afin de laisser le test exécuter une quantité suffisante de temps (10 000, 100 000 fois) et obtenir une sorte de moyenne. Il est important d'exécuter le code plusieurs fois, pas le programme. Afin d'écrire une méthode, et la boucle dans la principale méthode pour obtenir vos mesures.
  3. supprimer tous les débogage des trucs dans les assemblées et de laisser le code de fonctionner de manière autonome dans un communiqué de construire

24voto

finnw Points 24592

Il y a quelques optimisations possibles. Peut-être que le Java JIT est la scène et le CLR ne l'est pas.

Optimisation n ° 1:

(x % a == 0) && (x % b == 0) && ... && (x % z == 0)

est équivalent à

(x % lcm(a, b, ... z) == 0)

Donc dans votre exemple, la comparaison de la chaîne pourrait être remplacé par

if (i % 232792560 == 0) break;

(mais bien sûr, si vous avez déjà calculé la LCM, il y a peu de point dans l'exécution du programme en premier lieu!)

Optimisation n ° 2:

C'est aussi l'équivalent:

if (i % (14549535 * 16)) == 0 break;

ou

if ((i % 16 == 0) && (i % 14549535 == 0)) break;

La première division peut être remplacé par un masque et comparez-les avec zéro:

if (((i & 15) == 0) && (i % 14549535 == 0)) break;

La deuxième division peut être remplacé par une multiplication par l'inverse modulaire:

final long LCM = 14549535;
final long INV_LCM = 8384559098224769503L; // == 14549535**-1 mod 2**64
final long MAX_QUOTIENT = Long.MAX_VALUE / LCM;
// ...
if (((i & 15) == 0) &&
    (0 <= (i>>4) * INV_LCM) &&
    ((i>>4) * INV_LCM < MAX_QUOTIENT)) {
    break;
}

Il est donc peu probable que le JIT est l'emploi de cette, mais il n'est pas aussi farfelu que vous pourriez penser, certains compilateurs C mettre en œuvre le pointeur de la soustraction de cette façon.

12voto

ShuggyCoUk Points 24204

La clé pour faire de ces deux deviennent de plus près est de veiller à ce que la comparaison est juste.

Tout d'abord veiller à ce que les coûts associés à l'exécution de Debug, le chargement des symboles pdb comme vous l'avez fait.

Ensuite vous devez vous assurer qu'il n'y a pas d'init coûts comptabilisés. Évidemment ce sont des coûts réels, et de l'importance pour certaines personnes, mais dans ce cas, nous nous sommes intéressés à la boucle elle-même.

Ensuite, vous devez faire face avec la plate-forme de comportement spécifique. Si vous êtes sur une version 64bit de windows de la machine vous pouvez peut-être en cours d'exécution soit en 32 bits ou 64 bits mode. En 64 bits mode le JIT est différente à bien des égards, souvent modifiant le code résultant considérablement. Plus précisément, et je suppose que bien à propos, vous avez accès à deux fois comme beaucoup de registres.

Dans ce cas, la section intérieure de la boucle, quand naïvement traduit en code machine, il serait nécessaire de les charger dans des registres les constantes utilisées dans le modulo tests. Si il ya insuffisante pour contenir tout le nécessaire dans la boucle puis il doit les pousser à partir de la mémoire. Même de la part de niveau1 cache ce serait un grand hit par rapport à les garder dans les registres.

Dans VS 2010 MS changé la cible par défaut de anycpu pour x86. Je n'ai rien comme les ressources ou le client face à la connaissance de MSFT, donc je ne vais pas essayer de deuxième deviner que. Cependant toute personne à la recherche de quelque chose, comme l'analyse de la performance que vous faites devriez certainement essayer les deux.

Une fois que ces disparités sont aplanies les chiffres semblent plus raisonnables. Toute différence probablement mieux que des suppositions éclairées, à la place ils auraient besoin d'une enquête sur les différences réelles dans le code machine généré.

Il y a plusieurs choses à propos de cela, je pense que ce serait intéressant pour une optimisation du compilateur.

  • Ceux finnw déjà mentionné:
    • Le lcm option intéressante, mais je ne peux pas voir un compilateur écrivain déranger.
    • la réduction de la division de la multiplication et de masquage.
      • Je ne sais pas assez sur ce sujet, mais d'autres personnes ont essayé de noter qu'ils appellent le diviseur sur les plus récents processeurs intel nettement mieux.
      • Peut-être que vous pourriez même organiser quelque chose de complexe, avec SSE2.
      • Certainement le modulo 16 fonctionnement est mûre pour la conversion en un masque ou maj.
    • Un compilateur pu observer qu'aucun des essais ont des effets secondaires.
      • il pourrait éventuellement essayer d'évaluer plusieurs d'entre eux à la fois, un super processeur scalaire cela pourrait de la pompe les choses un peu plus rapide, mais dépendent fortement de la façon dont le compilateurs disposition interagi avec le OO moteur d'exécution.
    • Si vous inscrire à la pression était serré, vous pourriez mettre en œuvre les constantes comme une variable unique, établi au début de chaque tour de boucle puis incrémenter comme vous allez le long.

Ce sont tous de proférer des conjectures, et doit être considéré comme inactif méandres. Si vous voulez savoir de le démonter.

10voto

George Duckett Points 17305

DateTime n'est pas exacte pour le benchmarking (comme indiqué ici (lire les commentaires)), jetez un oeil sur le chronomètre de classe

1voto

BertNase Points 1435

Peut-être parce que la construction de l' DateTimeobjets est beaucoup plus cher que l' System.currentTimeMillis.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X