Est-ce que si (var == true) est plus rapide que si (var! = False)?

Question assez simple. Je sais que ce serait probablement une optimisation minuscule, mais vous finirez par en utiliser suffisamment si les déclarations sont importantes.

EDIT: Merci à ceux d’entre vous qui ont fourni des réponses.

Pour ceux d’entre vous qui ressentent le besoin de me bash, sachez que la curiosité et la soif de connaissances ne se traduisent pas en stupidité.

Et merci à tous ceux qui ont formulé des critiques constructives. Je n’avais aucune connaissance de la possibilité d’indiquer si (var) jusqu’à maintenant. Je suis sûr que je vais l’utiliser maintenant. 😉

Tout d’abord: la seule façon de répondre à la question de performance est de la mesurer . Essayez vous-même et vous saurez.

Quant à ce que fait le compilateur: je vous rappelle que “if” n’est qu’un goto conditionnel. Lorsque vous avez

 if (x) Y(); else Z(); Q(); 

le compilateur génère cela comme:

 evaluate x branch to LABEL1 if result was false call Y branch to LABEL2 LABEL1: call Z LABEL2: call Q 

ou

 evaluate !x branch to LABEL1 if result was true 

selon qu’il est plus facile de générer le code pour obtenir le résultat “normal” ou “inversé” quel que soit le “x” qui se produit. Par exemple, si vous avez if (a<=b) il pourrait être plus facile de le générer sous la forme (if !(a>b)) . Ou vice versa; cela dépend des détails du code exact compilé.

Quoi qu'il en soit, je suppose que vous avez de plus gros poissons à faire frire. Si vous vous souciez de la performance, utilisez un profileur pour trouver la solution la plus lente, puis corrigez-le . Il n’a aucun sens de s’inquiéter des optimisations en nanosecondes alors que vous gaspillez probablement des millisecondes entières ailleurs dans votre programme.

Saviez-vous que sur les processeurs x86, il est plus efficace de faire x ^= x où x est un entier de 32 bits, plutôt que de faire x = 0 ? C’est vrai et, bien sûr, le même résultat. Par conséquent, chaque fois que l’on peut voir x = 0 dans le code, on peut le remplacer par x ^= x et gagner en efficacité.

Maintenant, avez-vous déjà vu x ^= x dans beaucoup de code?

La raison pour laquelle vous ne l’avez pas fait n’est pas simplement parce que le gain d’efficacité est faible, mais parce que c’est précisément le type de changement qu’un compilateur (si comstackr en code natif) ou une gigue (si comstackr IL ou similaire) va faire. Désassemblez du code x86 et il n’est pas inhabituel de voir l’équivalent assemblé de x ^= x , bien que le code compilé pour ce faire ait presque certainement x = 0 ou peut-être quelque chose de beaucoup plus compliqué comme x = 4 >> 6 ou x = 32 - y où l’parsing du code montre que y contiendra toujours 32 à ce stade, et ainsi de suite.

Pour cette raison, même si x ^= x est connu pour être plus efficace, son seul effet dans la très grande majorité des cas serait de rendre le code moins lisible (la seule exception serait où x ^= y impliquait l’utilisation d’un algorithme et nous nous trouvions dans un cas où x et y étaient identiques, dans ce cas, x ^= x rendrait l’utilisation de cet algorithme plus claire, tandis que x = 0 le masquerait).

Dans 99,999999% des cas, il en va de même pour votre exemple. Dans les cas restants de 0,000001%, cela devrait être le cas, mais il existe une différence d’efficacité entre un type étrange de substitution d’opérateur et le compilateur ne peut pas résoudre l’un par l’autre. En effet, 0,000001% surestime le cas, et cela vient d’être mentionné car je suis à peu près sûr que si je faisais assez d’efforts, je pourrais écrire quelque chose où l’un est moins efficace que l’autre. Normalement, les gens n’essayent pas de le faire.

Si vous examinez votre propre code dans le réflecteur, vous constaterez probablement quelques cas où il est très différent du code que vous avez écrit. La raison en est qu’il s’agit de l’ingénierie inverse de l’IL de votre code, plutôt que de votre code lui-même. En effet, vous constaterez souvent que des choses comme if(var == true) ou if(var != false) sont transformé en if(var) ou même en if(!var) avec les blocs if et else inversés.

Regardez plus en profondeur et vous constaterez que des modifications supplémentaires sont apscopes dans la mesure où il existe plus d’une façon de traiter le même chat. En particulier, il est intéressant de voir comment les instructions switch sont converties en IL. parfois cela devient l’équivalent d’un tas de déclarations if-else if , et parfois cela devient une recherche dans une table de sauts qui pourraient être faits, en fonction de ce qui semblait plus efficace dans le cas en question.

Regardez plus profondément encore et d’autres modifications sont apscopes lorsqu’il est compilé en code natif.

Je ne serai pas d’accord avec ceux qui parlent d ‘”optimisation prématurée” simplement parce que vous posez des questions sur la différence de performances entre deux approches différentes, car la connaissance de telles différences est une bonne chose, elle consiste uniquement à utiliser prématurément cette connaissance prématurée (par définition). Mais un changement qui ne sera pas compilé ne sera ni prématuré, ni optimisé, mais un changement nul.

Cela ne fera aucune différence. En utilisant le réflecteur, vous pouvez voir que le code:

 private static void testVar(bool var) { if (var == true) { Console.WriteLine("test"); } if (var != false) { Console.WriteLine("test"); } if (var) { Console.WriteLine("test"); } } 

crée l’IL:

 .method private hidebysig static void testVar(bool var) cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: brfalse.s L_000d L_0003: ldstr "test" L_0008: call void [mscorlib]System.Console::WriteLine(ssortingng) L_000d: ldarg.0 L_000e: brfalse.s L_001a L_0010: ldstr "test" L_0015: call void [mscorlib]System.Console::WriteLine(ssortingng) L_001a: ldarg.0 L_001b: brfalse.s L_0027 L_001d: ldstr "test" L_0022: call void [mscorlib]System.Console::WriteLine(ssortingng) L_0027: ret } 

Ainsi, le compilateur (en .Net 3.5) les traduit tous dans le jeu d’instructions ldarg.0, brfalse.s.

Cela ne fera aucune différence, car le compilateur comstackrait certainement les deux instructions en utilisant le même code binary.

Le (pseudo) assemblage sera soit:

 test reg1, reg2 br.true somewhere ; code for false case somewhere: ; code for true case 

ou

 test reg1, reg2 br.false somewhere ; code for true case somewhere: ; code for false case 

Le choix du compilateur ne dépendra pas de savoir si vous écrivez == true ou != false . C’est plutôt une optimisation que le compilateur fera en fonction de la taille du code de cas vrai et faux et peut-être de quelques autres facteurs.

En passant, le code du kernel Linux tente effectivement d’optimiser ces twigs en utilisant les macros UNLIKELY et UNLIKELY pour ses conditions if , donc je suppose qu’il est possible de le contrôler manuellement.

Peu importe le nombre d’itérations que vous utilisez dans votre programme, cela ne fait aucune différence mesurable.

(Utilisez if (var) place; vous n’avez pas besoin du fouillis visuel des comparaisons.)

Une règle empirique qui fonctionne habituellement est “Si vous savez qu’ils font la même chose, le compilateur le sait aussi”.

Si le compilateur sait que les deux formes donnent le même résultat, il choisira la plus rapide .

Par conséquent, supposons qu’ils soient tout aussi rapides, jusqu’à ce que votre profileur vous dise le contraire.

Toujours optimiser pour faciliter la compréhension . C’est une règle de programmation cardinale, en ce qui me concerne. Vous ne devez pas micro-optimiser, ni même optimiser du tout, jusqu’à ce que vous sachiez que vous devez le faire et où vous devez le faire. Il est très rare que réduire chaque once de performances soit plus important que la facilité de maintenance. Il est encore plus rare que vous soyez si impressionnant que vous sachiez où optimiser au moment de l’écriture initiale du code.

De plus, de telles choses sont automatiquement optimisées dans toutes les langues.

tl; dr ne vous embêtez pas

Les autres réponses sont toutes bonnes, je voulais juste append:

Cette question n’a pas de sens, car elle suppose une relation de 1: 1 entre la notation et le code IL ou natif obtenu.

Il n’y a pas. Et cela est vrai même en C ++, et même en C. Il faut aller jusqu’au code natif pour qu’une telle question ait un sens.

Édité pour append:

Les développeurs du premier compilateur Fortran (vers 1957) ont été surpris un jour en examinant les résultats. C’était un code qui n’était pas manifestement correct (même si c’était le cas); en substance, il prenait des décisions d’optimisation qui n’étaient pas manifestement correctes (bien qu’elles fussent).

La morale de cette histoire: les compilateurs sont plus intelligents que les gens depuis plus de 50 ans. N’essayez pas de les déjouer si vous n’êtes pas prêt à examiner leurs résultats et / ou à effectuer des tests de performances approfondis.

Cela ne fait aucune différence et le compilateur est libre de les échanger à volonté.

Par exemple, vous pourriez écrire

 if (!predicate) statement1; else statement2; 

et le compilateur est libre d’émettre un code équivalent à

 if (predicate) statement2; else statement1; 

ou vice versa.

Savoir lequel de ces deux cas spécifiques est le plus rapide est un niveau de détail qui est rarement (voire jamais) requirejs dans un langage de haut niveau. Peut-être aurez-vous besoin de le savoir si votre compilateur est pissé aux optimisations. Cependant, si votre compilateur est si mauvais, vous auriez probablement intérêt à en obtenir un meilleur si possible.

Si vous programmez en assemblage, il est plus probable que la connaissance des deux cas soit meilleure. D’autres ont déjà donné une ventilation de l’assemblage en ce qui concerne les déclarations de twig. Je ne vais donc pas prendre la peine de répéter cette partie de la réponse. Cependant, un élément qui a été omis à mon avis est celui de la comparaison.

Il est concevable qu’un processeur puisse modifier les indicateurs d’état lors du chargement de ‘var’. Si tel est le cas, alors si ‘var’ vaut 0, l’indicateur zéro peut être défini lorsque la variable est chargée dans un registre. Avec un tel processeur, aucune comparaison explicite avec FALSE ne serait requirejse. Le pseudo-code d’assemblage équivalent serait …

 load 'var' into register branch if zero or if not zero as appropriate 

En utilisant ce même processeur, si vous deviez le tester contre TRUE, le pseudo-code d’assemblage serait …

 load 'var' into register compare that register to TRUE (a specific value) branch if equal or if not equal as appropriate 

En pratique, certains processeurs se comportent-ils de la sorte? Je ne sais pas – d’autres seront plus informés que moi. Je connais des personnes qui ne se comportent pas de cette façon, mais je ne connais pas tout.

En supposant que certains processeurs se comportent comme dans le scénario décrit ci-dessus, que pouvons-nous apprendre? SI (et c’est un gros SI) vous allez vous inquiéter à ce sujet, évitez de tester les booléens contre des valeurs explicites …

 if (var == TRUE) if (var != FALSE) 

et utilisez l’une des méthodes suivantes pour tester les types booléens …

 if (var) if (!var)