J'ai lu partout que l'opérateur ternaire est censé être plus rapide que, ou au moins le même que son équivalent if
-else
bloc.
Cependant, j'ai fait le test suivant et j'ai découvert que ce n'était pas le cas:
Random r = new Random();
int[] array = new int[20000000];
for(int i = 0; i < array.Length; i++)
{
array[i] = r.Next(int.MinValue, int.MaxValue);
}
Array.Sort(array);
long value = 0;
DateTime begin = DateTime.UtcNow;
foreach (int i in array)
{
if (i > 0)
{
value += 2;
}
else
{
value += 3;
}
// if-else block above takes on average 85 ms
// OR I can use a ternary operator:
// value += i > 0 ? 2 : 3; // takes 157 ms
}
DateTime end = DateTime.UtcNow;
MessageBox.Show("Measured time: " + (end-begin).TotalMilliseconds + " ms.\r\nResult = " + value.ToString());
Mon ordinateur a mis 85 ms pour exécuter le code ci-dessus. Mais si je commente le if
- else
morceau et décommente la ligne d'opérateur ternaire, cela prendra environ 157 ms.
Pourquoi cela arrive-t-il?
c#
performance
conditional-operator
user1032613
la source
la source
DateTime
pour mesurer les performances. UtilisezStopwatch
. Ensuite, le temps est plus long - c'est un temps très court pour mesurer.Random
objet, afin qu'il donne toujours la même séquence. Si vous testez un code différent avec des données différentes, vous pouvez très bien voir les différences de performances.Réponses:
Pour répondre à cette question, nous examinerons le code assembleur produit par les JIT X86 et X64 pour chacun de ces cas.
X86, si / alors
X86, ternaire
X64, si / alors
X64, ternaire
Premièrement: pourquoi le code X86 tellement plus lent que X64?
Cela est dû aux caractéristiques suivantes du code:
i
partir de la matrice, tandis que le JIT X86 place plusieurs opérations de pile (accès mémoire) dans la boucle.value
est un entier 64 bits, qui nécessite 2 instructions machine sur X86 (add
suivies deadc
) mais seulement 1 sur X64 (add
).Deuxièmement: pourquoi l'opérateur ternaire est-il plus lent sur X86 et X64?
Cela est dû à une différence subtile dans l'ordre des opérations impactant l'optimiseur du JIT. Pour JIT l'opérateur ternaire, plutôt que de coder directement
2
et3
dans lesadd
instructions de la machine elles-mêmes, le JIT crée une variable intermédiaire (dans un registre) pour contenir le résultat. Ce registre est ensuite étendu de 32 bits à 64 bits avant d'être ajouté àvalue
. Étant donné que tout cela est effectué dans les registres pour X64, malgré l'augmentation significative de la complexité pour l'opérateur ternaire, l'impact net est quelque peu minimisé.D'un autre côté, le X86 JIT est impacté car l'ajout d'une nouvelle valeur intermédiaire dans la boucle interne le fait "déborder" d'une autre valeur, ce qui entraîne au moins 2 accès mémoire supplémentaires dans la boucle interne (voir les accès pour
[ebp-14h]
le code ternaire X86).la source
EDIT: Tout change ... voir ci-dessous.
Je ne peux pas reproduire vos résultats sur le x64 CLR, mais je peux le faire sur x86. Sur x64, je peux voir un petit différence (moins de 10%) entre l'opérateur conditionnel et le if / else, mais il est beaucoup plus petit que ce que vous voyez.
J'ai apporté les modifications potentielles suivantes:
/o+ /debug-
, et exécutez en dehors du débogueurStopwatch
Résultats avec
/platform:x64
(sans les lignes "ignorer"):Résultats avec
/platform:x86
(sans les lignes "ignorer"):Détails de mon système:
Donc , contrairement à avant, je pense que vous êtes voyez une réelle différence - et il est tout à voir avec le x86 JIT. Je ne voudrais pas dire exactement quoi cause la différence - je pourrais mettre à jour le message plus tard avec plus de détails si je peux me donner la peine d'aller dans cordbg :)
Fait intéressant, sans trier d'abord le tableau, je me retrouve avec des tests qui prennent environ 4,5 fois plus longtemps, au moins sur x64. Je suppose que cela a à voir avec la prédiction de branche.
Code:
la source
La différence n'a vraiment pas grand-chose à voir avec if / else vs ternaire.
En regardant les démontages jitted (je ne vais pas répéter ici, veuillez voir la réponse de @ 280Z28), il s'avère que vous comparez des pommes et des oranges . Dans un cas, vous créez deux
+=
opérations différentes avec des valeurs constantes et celle que vous choisissez dépend d'une condition, et dans l'autre cas, vous créez un+=
où la valeur à ajouter dépend d'une condition.Si vous voulez vraiment comparer if / else vs ternaire, ce serait une comparaison plus juste (maintenant les deux seront également "lents", ou on pourrait même dire que le ternaire est un peu plus rapide):
contre.
Maintenant, le démontage du
if/else
devient comme indiqué ci-dessous. Notez que c'est un peu pire que le cas ternaire, car il a également cessé d'utiliser les registres pour la variable de boucle (i
).la source
diff
, mais ternaire est encore beaucoup plus lent - pas du tout ce que vous avez dit. Avez-vous fait l'expérience avant de poster cette "réponse"?Éditer:
Ajout d'un exemple qui peut être fait avec l'instruction if-else mais pas l'opérateur conditionnel.
Avant la réponse, veuillez jeter un œil à [ Quel est le plus rapide? ] sur le blog de M. Lippert. Et je pense que la réponse de M. Ersönmez est la plus juste ici.
J'essaie de mentionner quelque chose que nous devons garder à l'esprit avec un langage de programmation de haut niveau.
Tout d'abord, je n'ai jamais entendu dire que l'opérateur conditionnel est censé être plus rapide ou la même performance avec l'instruction if-else en C♯ .
La raison est simple: que faire s'il n'y a pas d'opération avec l'instruction if-else:
L'exigence de l'opérateur conditionnel est qu'il doit y avoir une valeur de chaque côté, et en C♯, il faut également que les deux côtés de
:
aient le même type. Cela le rend juste différent de l'instruction if-else. Ainsi, votre question devient une question demandant comment l'instruction du code machine est générée pour que la différence de performance.Avec l'opérateur conditionnel, sémantiquement c'est:
Quelle que soit l'expression évaluée, il y a une valeur.
Mais avec l'instruction if-else:
Si l'expression est évaluée à true, faites quelque chose; sinon, faites autre chose.
Une valeur n'est pas nécessairement impliquée dans l'instruction if-else. Votre hypothèse n'est possible qu'avec l'optimisation.
Un autre exemple pour démontrer la différence entre eux serait le suivant:
le code ci-dessus compile, cependant, remplace l'instruction if-else par l'opérateur conditionnel ne compilera tout simplement pas:
L'opérateur conditionnel et les instructions if-else sont conceptuellement les mêmes lorsque vous faites la même chose, cela peut même être plus rapide avec l'opérateur conditionnel en C , car C est plus proche de l'assemblage de la plate-forme.
Pour le code d'origine que vous avez fourni, l'opérateur conditionnel est utilisé dans une boucle foreach, ce qui gâcherait les choses pour voir la différence entre eux. Je propose donc le code suivant:
et ce qui suit sont deux versions de l'IL optimisé et non. Puisqu'ils sont longs, j'utilise une image pour montrer, le côté droit est optimisé:
Dans les deux versions de code, l'IL de l'opérateur conditionnel semble plus court que l'instruction if-else, et il existe toujours un doute sur le code machine finalement généré. Voici les instructions des deux méthodes, et l'ancienne image n'est pas optimisée, la dernière est optimisée:
Instructions non optimisées: (Cliquez pour voir l'image en taille réelle.)
Instructions optimisées: (Cliquez pour voir l'image en taille réelle.)
Dans ce dernier, le bloc jaune est le code exécuté uniquement si
i<=0
, et le bloc bleu est quandi>0
. Dans l'une ou l'autre version des instructions, l'instruction if-else est plus courte.Notez que, pour des instructions différentes, le [ CPI ] n'est pas nécessairement le même. Logiquement, pour une instruction identique, plus d'instructions coûtent un cycle plus long. Mais si le temps de récupération des instructions et le canal / cache étaient également pris en compte, le temps total réel d'exécution dépend du processeur. Le processeur peut également prédire les branches.
Les processeurs modernes ont encore plus de cœurs, les choses peuvent être plus complexes avec cela. Si vous étiez un utilisateur de processeur Intel, vous voudrez peut-être consulter le [ Manuel de référence de l'optimisation des architectures Intel® 64 et IA-32 ].
Je ne sais pas s'il y avait un CLR implémenté par le matériel, mais si oui, vous obtenez probablement plus rapidement avec l'opérateur conditionnel car l'IL est évidemment moindre.
Remarque: Tous les codes machine sont de x86.
la source
J'ai fait ce que Jon Skeet a fait et j'ai effectué 1 itération et 1 000 itérations et j'ai obtenu un résultat différent à la fois pour OP et Jon. Dans le mien, le ternaire est légèrement plus rapide. Voici le code exact:
La sortie de mon programme:
Une autre course en millisecondes:
Cela fonctionne sous XP 64 bits, et j'ai couru sans débogage.
Édition - Exécution en x86:
Il y a une grande différence avec x86. Cela a été fait sans débogage sur et sur la même machine xp 64 bits qu'avant, mais conçu pour les processeurs x86. Cela ressemble plus à des OP.
la source
Le code assembleur généré racontera l'histoire:
Génère:
Tandis que:
Génère:
Ainsi, le ternaire peut être plus court et plus rapide simplement en raison de l'utilisation de moins d'instructions et d'aucun saut si vous recherchez vrai / faux. Si vous utilisez des valeurs autres que 1 et 0, vous obtiendrez le même code qu'un if / else, par exemple:
Génère:
Ce qui est le même que le if / else.
la source
Exécuter sans déboguer ctrl + F5, il semble que le débogueur ralentisse significativement ifs et ternaire, mais il semble qu'il ralentisse beaucoup plus l'opérateur ternaire.
Lorsque j'exécute le code suivant, voici mes résultats. Je pense que la petite différence de millisecondes est causée par le compilateur optimisant le max = max et le supprimant, mais ne fait probablement pas cette optimisation pour l'opérateur ternaire. Si quelqu'un pouvait vérifier l'assemblage et le confirmer, ce serait génial.
Code
la source
En regardant l'IL généré, il y a 16 opérations de moins dans cela que dans l'instruction if / else (copier et coller le code de @ JonSkeet). Cependant, cela ne signifie pas que ce devrait être un processus plus rapide!
Pour résumer les différences dans IL, la méthode if / else se traduit à peu près de la même manière que le code C # lit (effectuant l'ajout dans la branche) tandis que le code conditionnel charge 2 ou 3 sur la pile (selon la valeur) et l'ajoute ensuite à la valeur en dehors du conditionnel.
L'autre différence est l'instruction de branchement utilisée. La méthode if / else utilise une brtrue (branch if true) pour sauter par-dessus la première condition, et une branche inconditionnelle pour sauter de la première sortie de l'instruction if. Le code conditionnel utilise un bgt (branche si supérieur à) au lieu d'un brtrue, ce qui pourrait éventuellement être une comparaison plus lente.
De plus (après avoir simplement lu sur la prédiction de branche), il peut y avoir une pénalité de performance pour la branche plus petite. La branche conditionnelle a seulement 1 instruction dans la branche mais le if / else en a 7. Cela expliquerait également pourquoi il y a une différence entre utiliser long et int, car le passage à un int réduit le nombre d'instructions dans les branches if / else de 1 (réduisant la lecture anticipée)
la source
Dans le code suivant, if / else semble être environ 1,4 fois plus rapide que l'opérateur ternaire. Cependant, j'ai trouvé que l'introduction d'une variable temporaire diminue le temps d'exécution de l'opérateur ternaire d'environ 1,4 fois:
la source
Trop de bonnes réponses mais j'ai trouvé quelque chose d'intéressant, des changements très simples font l'impact. Après avoir effectué le changement ci-dessous, pour exécuter l'opérateur if-else et ternaire, cela prendra le même temps.
au lieu d'écrire sous la ligne
J'ai utilisé ça,
L'une des réponses ci-dessous mentionne également que ce qui est une mauvaise façon d'écrire un opérateur ternaire.
J'espère que cela vous aidera à écrire l'opérateur ternaire, au lieu de penser à celui qui est le meilleur.
Opérateur ternaire imbriqué: J'ai trouvé un opérateur ternaire imbriqué et plusieurs blocs if else prendront également le même temps pour s'exécuter.
la source