Est-ce que le code
int a = ((1 + 2) + 3); // Easy to read
courir plus lentement que
int a = 1 + 2 + 3; // (Barely) Not quite so easy to read
ou les compilateurs modernes sont-ils assez intelligents pour supprimer / optimiser les parenthèses "inutiles".
Cela peut sembler être une toute petite préoccupation d'optimisation, mais choisir C ++ plutôt que C # / Java / ... est une question d'optimisations (IMHO).
c++
optimization
compilation
Serge
la source
la source
Réponses:
Le compilateur n'insère ni ne supprime jamais de parenthèses; il crée simplement un arbre d'analyse (dans lequel aucune parenthèse n'est présente) correspondant à votre expression, et ce faisant, il doit respecter les parenthèses que vous avez écrites. Si vous mettez complètement votre expression entre parenthèses, il sera également immédiatement clair pour le lecteur humain ce qu'est cet arbre d'analyse; si vous allez à l'extrême de mettre des parenthèses manifestement redondantes comme dans
int a = (((0)));
alors vous causerez un stress inutile sur les neurones du lecteur tout en gaspillant certains cycles dans l'analyseur, sans toutefois changer l'arbre d'analyse résultant (et donc le code généré ) le moins du monde.Si vous n'écrivez pas de parenthèses, alors l'analyseur doit quand même faire son travail de création d'un arbre d'analyse, et les règles de priorité et d'associativité des opérateurs lui indiquent exactement quel arbre d'analyse il doit construire. Vous pouvez considérer ces règles comme indiquant au compilateur quelles parenthèses (implicites) il doit insérer dans votre code, bien que l'analyseur ne traite jamais les parenthèses dans ce cas: il a juste été construit pour produire le même arbre d'analyse que si des parenthèses étaient présents à certains endroits. Si vous placez des parenthèses exactement à ces endroits, comme dans
int a = (1+2)+3;
(l'associativité de+
est à gauche), l'analyseur arrivera au même résultat par un itinéraire légèrement différent. Si vous mettez des parenthèses différentes comme dansint a = 1+(2+3);
alors vous forcez un arbre d'analyse différent, ce qui entraînera éventuellement la génération de code différent (mais peut-être pas, car le compilateur peut appliquer des transformations après la construction de l'arbre d'analyse, tant que l' effet de l'exécution du code résultant ne sera jamais différent pour il). En supposant qu'il y ait une différence dans le code de resuting, rien en général ne peut être dit quant à ce qui est plus efficace; le point le plus important est bien sûr que la plupart du temps les arbres d'analyse ne donnent pas d'expressions mathématiquement équivalentes, donc comparer leur vitesse d'exécution est à côté du point: il suffit d'écrire l'expression qui donne le résultat correct.Le résultat est donc: utilisez des parenthèses au besoin pour l'exactitude et comme vous le souhaitez pour la lisibilité; s'ils sont redondants, ils n'ont aucun effet sur la vitesse d'exécution (et un effet négligeable sur le temps de compilation).
Et rien de tout cela n'a quoi que ce soit à voir avec l'optimisation , qui survient après la construction de l'arbre d'analyse, il ne peut donc pas savoir comment l'arbre d'analyse a été construit. Cela s'applique sans changement des compilateurs les plus anciens et les plus stupides aux compilateurs les plus intelligents et les plus modernes. Ce n'est que dans un langage interprété (où "temps de compilation" et "temps d'exécution" coïncident) qu'il pourrait y avoir une pénalité pour les parenthèses redondantes, mais même dans ce cas, je pense que la plupart de ces langages sont organisés de manière à ce qu'au moins la phase d'analyse ne soit effectuée qu'une seule fois pour chaque instruction (en stockant une forme pré-analysée pour exécution).
la source
a = b + c * d;
,a = b + (c * d);
serait des parenthèses redondantes [inoffensives]. S'ils vous aident à rendre le code plus lisible, très bien.a = (b + c) * d;
serait des parenthèses non redondantes - elles modifient en fait l'arbre d'analyse résultant et donnent un résultat différent. Parfaitement légal à faire (en fait, nécessaire), mais ils ne sont pas les mêmes que le regroupement implicite implicite.Les parenthèses sont là uniquement pour votre avantage - pas les compilateurs. Le compilateur créera le code machine correct pour représenter votre déclaration.
Pour info, le compilateur est suffisamment intelligent pour l'optimiser complètement s'il le peut. Dans vos exemples, cela se transformerait
int a = 6;
au moment de la compilation.la source
int a = 8;// = 2*3 + 5
int five = 7; //HR made us change this to six...
La réponse à la question que vous avez réellement posée est non, mais la réponse à la question que vous vouliez poser est oui. L'ajout de parenthèses ne ralentit pas le code.
Vous avez posé une question sur l'optimisation, mais les parenthèses n'ont rien à voir avec l'optimisation. Le compilateur applique une variété de techniques d'optimisation dans le but d'améliorer la taille ou la vitesse du code généré (parfois les deux). Par exemple, il peut prendre l'expression A ^ 2 (A au carré) et la remplacer par A x A (A multiplié par lui-même) si c'est plus rapide. La réponse est non, le compilateur ne fait rien de différent dans sa phase d'optimisation selon que les parenthèses sont présentes ou non.
Je pense que vous vouliez vous demander si le compilateur génère toujours le même code si vous ajoutez des parenthèses inutiles à une expression, à des endroits qui, selon vous, pourraient améliorer la lisibilité. En d'autres termes, si vous ajoutez des parenthèses, le compilateur est suffisamment intelligent pour les supprimer à nouveau plutôt que de générer en quelque sorte un code plus pauvre. La réponse est oui, toujours.
Permettez-moi de le dire avec soin. Si vous ajoutez des parenthèses à une expression qui sont strictement inutiles (n'ont aucun effet sur la signification ou l'ordre d'évaluation d'une expression), le compilateur les supprimera en silence et générera le même code.
Cependant, il existe certaines expressions où des parenthèses apparemment inutiles changeront réellement l'ordre d'évaluation d'une expression et dans ce cas, le compilateur générera du code pour mettre en œuvre ce que vous avez réellement écrit, qui pourrait être différent de ce que vous vouliez. Voici un exemple. Ne fais pas ça!
Ajoutez donc des parenthèses si vous le souhaitez, mais assurez-vous qu'elles ne sont vraiment pas nécessaires!
Je ne fais jamais. Il existe un risque d'erreur sans réel avantage.
Note: un comportement non signé se produit lorsqu'une expression d'entier signé évalue à une valeur qui est en dehors de la plage qu'elle peut exprimer, dans ce cas -32767 à +32767. Il s'agit d'un sujet complexe, hors de portée de cette réponse.
la source
a
peut vraiment ne pas être signé, mener votre calcul avec-a + b
pourrait facilement déborder également s'ila
était négatif etb
positif.(b+c)
dans la dernière ligne va promouvoir ses argumentsint
, donc à moins que le compilateur ne définisseint
comme 16 bits (soit parce qu'il est ancien ou cible un petit microcontrôleur), la dernière ligne serait parfaitement légitime.int
est promu àint
moins que ce type ne soit pas en mesure de représenter toutes ses valeurs, auquel cas il est promuunsigned int
. Les compilateurs peuvent omettre les promotions si tous les comportements définis sont les mêmes que si les promotions étaient incluses . Sur une machine où les types 16 bits se comportent comme un anneau algébrique abstrait enveloppant, (a + b) + c et a + (b + c) seront équivalents. Siint
un type 16 bits était piégé par le débordement, cependant, il y aurait des cas où l'une des expressions ...Les crochets ne sont là que pour vous permettre de manipuler l'ordre de priorité des opérateurs. Une fois compilés, les crochets n'existent plus car le runtime n'en a pas besoin. Le processus de compilation supprime tous les crochets, les espaces et tout autre sucre syntaxique dont vous et moi avons besoin et transforme tous les opérateurs en quelque chose de [beaucoup] plus simple à exécuter par l'ordinateur.
Alors, où vous et moi pourrions voir ...
... un compilateur peut émettre quelque chose de plus comme ceci:
Le programme est exécuté en commençant au début et en exécutant chaque instruction tour à tour.
La priorité des opérateurs est désormais "premier arrivé, premier servi".
Tout est fortement typé, car le compilateur a travaillé tout ce que pendant que ce déchirait la syntaxe originale.
OK, ça n'a rien à voir avec ce que vous et moi traitons, mais nous ne le faisons pas!
la source
Dépend s'il est en virgule flottante ou non:
En virgule flottante, l'addition n'est pas associative, l'optimiseur ne peut donc pas réorganiser les opérations (sauf si vous ajoutez le commutateur du compilateur fastmath).
Dans les opérations entières, ils peuvent être réorganisés.
Dans votre exemple, les deux s'exécuteront exactement en même temps car ils seront compilés avec le même code exact (l'addition est évaluée de gauche à droite).
cependant, même java et C # pourront l'optimiser, ils le feront juste au moment de l'exécution.
la source
Le compilateur C ++ typique se traduit par du code machine, pas par C ++ lui-même . Cela supprime les parens inutiles, oui, car au moment où c'est fait, il n'y a plus de parens. Le code machine ne fonctionne pas de cette façon.
la source
Les deux codes sont codés en dur 6:
Vérifiez par vous-même ici
la source
Non, mais oui, mais peut-être, mais peut-être dans l'autre sens, mais non.
Comme les gens l'ont déjà souligné (en supposant un langage où l'addition est associative à gauche, comme C, C ++, C # ou Java), l'expression
((1 + 2) + 3)
est exactement équivalente à1 + 2 + 3
. Ce sont différentes façons d'écrire quelque chose dans le code source, qui n'auraient aucun effet sur le code machine ou le code d'octet résultant.Dans les deux cas, le résultat sera une instruction pour, par exemple, ajouter deux registres, puis ajouter un troisième, ou prendre deux valeurs dans une pile, l'ajouter, la repousser, puis la prendre et une autre et les ajouter, ou ajouter trois registres dans une seule opération, ou une autre façon de additionner trois nombres en fonction de ce qui est le plus sensible au niveau suivant (le code machine ou le code octet). Dans le cas du code d'octet, celui-ci subira probablement à son tour une restructuration similaire en ce que, par exemple, l'équivalent IL de celui-ci (qui serait une série de charges dans une pile, et des paires éclatées pour ajouter puis repousser le résultat) n'entraînerait pas une copie directe de cette logique au niveau du code machine, mais quelque chose de plus sensé pour la machine en question.
Mais il y a quelque chose de plus à votre question.
Dans le cas de n'importe quel compilateur sain C, C ++, Java ou C #, je m'attendrais à ce que le résultat des deux instructions que vous donnez ait exactement les mêmes résultats que:
Pourquoi le code résultant devrait-il perdre du temps à faire des calculs sur les littéraux? Aucun changement à l'état du programme arrêteront le résultat d'
1 + 2 + 3
être6
, de sorte que ce qui devrait aller dans le code en cours d' exécution. En effet, peut-être même pas cela (selon ce que vous faites avec ce 6, nous pouvons peut-être tout jeter; et même C # avec sa philosophie de "ne pas optimiser fortement, car la gigue optimisera cela de toute façon" produira l'équivalent deint a = 6
ou jetez le tout comme inutile).Cela nous amène cependant à une éventuelle extension de votre question. Considérer ce qui suit:
et
(Remarque, ces deux derniers exemples ne sont pas valides en C #, alors que tout le reste est ici, et ils sont valides en C, C ++ et Java.)
Ici encore, nous avons un code exactement équivalent en termes de sortie. Comme ce ne sont pas des expressions constantes, elles ne seront pas calculées au moment de la compilation. Il est possible qu'une forme soit plus rapide qu'une autre. Lequel est plus vite? Cela dépendrait du processeur et peut-être de certaines différences d'état plutôt arbitraires (d'autant plus que si l'un est plus rapide, il est peu probable qu'il soit beaucoup plus rapide).
Et ils ne sont pas entièrement sans rapport avec votre question, car ils concernent principalement des différences dans l'ordre dans lequel quelque chose est conceptuellement fait.
Dans chacun d'eux, il y a une raison de soupçonner que l'un peut être plus rapide que l'autre. Les décrémentations simples peuvent avoir une instruction spécialisée, donc
(b / 2)--
pourraient en effet être plus rapides que(b - 2) / 2
.d * 32
pourrait peut-être être produit plus rapidement en le transformant end << 5
rendant ainsid * 32 - d
plus rapide qued * 31
. Les différences entre les deux derniers sont particulièrement intéressantes; l'un permet de sauter certains traitements dans certains cas, mais l'autre évite la possibilité d'une mauvaise prédiction de branchement.Donc, cela nous laisse avec deux questions: 1. L'un est-il réellement plus rapide que l'autre? 2. Un compilateur convertira-t-il le plus lent en le plus rapide?
Et la réponse est 1. Cela dépend. 2. Peut-être.
Ou pour étendre, cela dépend car cela dépend du processeur en question. Il y a certainement eu des processeurs où l'équivalent naïf de code machine de l'un serait plus rapide que l'équivalent naïf de code machine de l'autre. Au cours de l'histoire de l'informatique électronique, il n'y en a pas eu un qui a toujours été le plus rapide non plus (l'élément de prédiction erronée des branches en particulier n'était pas pertinent pour beaucoup lorsque les processeurs non pipelinés étaient plus courants).
Et peut-être, parce qu'il y a un tas d'optimisations différentes que les compilateurs (et la gigue et les moteurs de script) feront, et bien que certains puissent être obligatoires dans certains cas, nous serons généralement en mesure de trouver des morceaux de code logiquement équivalent qui même le compilateur le plus naïf a exactement les mêmes résultats et quelques morceaux de code logiquement équivalent où même le plus sophistiqué produit un code plus rapide pour l'un que pour l'autre (même si nous devons écrire quelque chose de totalement pathologique juste pour prouver notre point).
Non. Même avec des différences plus compliquées que celles que je donne ici, cela semble être une préoccupation absolument minuscule qui n'a rien à voir avec l'optimisation. En fait, c'est une question de pessimisation, car vous pensez que le plus difficile à lire
((1 + 2) + 3
pourrait être plus lent que le plus facile à lire1 + 2 + 3
.Si c'est vraiment de cela qu'il s'agissait de choisir C ++ plutôt que C # ou Java, je dirais que les gens devraient graver leur copie de Stroustrup et ISO / IEC 14882 et libérer de l'espace de leur compilateur C ++ pour laisser de la place pour d'autres MP3 ou quelque chose.
Ces langues ont des avantages différents les unes par rapport aux autres.
L'un d'eux est que C ++ est toujours généralement plus rapide et plus léger lors de l'utilisation de la mémoire. Oui, il existe des exemples où C # et / ou Java sont plus rapides et / ou ont une meilleure utilisation de la mémoire pendant la durée de vie des applications, et ceux-ci deviennent de plus en plus courants à mesure que les technologies impliquées s'améliorent, mais nous pouvons toujours nous attendre à ce que le programme moyen écrit en C ++ soit un exécutable plus petit qui fait son travail plus rapidement et utilise moins de mémoire que l'équivalent dans l'un de ces deux langages.
Ce n'est pas de l'optimisation.
Optimisation est parfois utilisée pour signifier "accélérer les choses". C'est compréhensible, car souvent, lorsque nous parlons vraiment d '"optimisation", nous parlons en effet d'accélérer les choses, et donc l'un est devenu un raccourci pour l'autre et j'avoue que j'utilise le mot de cette façon moi-même.
Le mot correct pour "accélérer les choses" n'est pas optimisation . Le mot correct ici est amélioration . Si vous apportez une modification à un programme et que la seule différence significative est qu'il est maintenant plus rapide, il n'est en aucun cas optimisé, c'est juste mieux.
L'optimisation est lorsque nous apportons une amélioration en ce qui concerne un aspect particulier et / ou un cas particulier. Des exemples courants sont:
De tels cas seraient justifiés si, par exemple:
Mais, de tels cas ne seraient pas non plus justifiés dans d'autres scénarios: le code n'a pas été amélioré par une mesure de qualité infaillible absolue, il a été amélioré à un égard particulier qui le rend plus adapté à un usage particulier; optimisé.
Et le choix de la langue a un effet ici, car la vitesse, l'utilisation de la mémoire et la lisibilité peuvent toutes en être affectées, mais la compatibilité avec d'autres systèmes, la disponibilité des bibliothèques, la disponibilité des temps d'exécution, la maturité de ces temps d'exécution sur un système d'exploitation donné peuvent également en être affectés. (Pour mes péchés, j'ai en quelque sorte fini par avoir Linux et Android comme mon système d'exploitation préféré et C # comme mon langage préféré, et même si Mono est génial, mais je me heurte toujours assez à celui-ci).
Dire "choisir C ++ plutôt que C # / Java / ... est une question d'optimisation" n'a de sens que si vous pensez que C ++ est vraiment nul, car l'optimisation est à propos de "mieux malgré ..." et non "mieux". Si vous pensez que le C ++ est meilleur malgré lui, alors la dernière chose dont vous avez besoin est de vous inquiéter des micro-opts possibles aussi minuscules. En effet, vous feriez probablement mieux de l'abandonner du tout; les hackers heureux sont aussi une qualité à optimiser!
Si toutefois, vous êtes enclin à dire "j'aime le C ++, et l'une des choses que j'aime à ce sujet, c'est d'éliminer les cycles supplémentaires", alors c'est une autre affaire. Il est toujours vrai que les micro-opérations ne valent la peine que si elles peuvent être une habitude réflexive (c'est-à-dire que la façon dont vous avez tendance à coder naturellement sera plus rapide plus souvent que lente). Sinon, ce n'est même pas une optimisation prématurée, c'est une pessimisation prématurée qui ne fait qu'empirer les choses.
la source
Les parenthèses sont là pour indiquer au compilateur dans quel ordre d'expressions doivent être évaluées. Parfois, ils sont inutiles (sauf qu'ils améliorent ou aggravent la lisibilité), car ils spécifient l'ordre qui serait utilisé de toute façon. Parfois, ils changent l'ordre. Dans
pratiquement toutes les langues existantes ont une règle selon laquelle la somme est évaluée en ajoutant 1 + 2, puis en ajoutant le résultat plus 3. Si vous avez écrit
alors la parenthèse forcerait un ordre différent: en ajoutant d'abord 2 + 3, puis en ajoutant 1 plus le résultat. Votre exemple de parenthèses produit le même ordre qui aurait été produit de toute façon. Maintenant, dans cet exemple, l'ordre des opérations est légèrement différent, mais la façon dont fonctionne l'addition d'entiers, le résultat est le même. Dans
les parenthèses sont critiques; les laisser de côté changerait le résultat de 9 à 1.
Une fois que le compilateur a déterminé quelles opérations sont effectuées dans quel ordre, les parenthèses sont complètement oubliées. Tout ce dont le compilateur se souvient à ce stade est de savoir quelles opérations effectuer dans quel ordre. Il n'y a donc rien que le compilateur puisse optimiser ici, les parenthèses ont disparu .
la source
practically every language in existence
; sauf APL: Essayez (ici) [tryapl.org] en entrant(1-2)+3
(2),1-(2+3)
(-4) et1-2+3
(également -4).Je suis d'accord avec une grande partie de ce qui a été dit, cependant ... le plus important ici est que les parenthèses sont là pour contraindre l'ordre de fonctionnement ... ce que le compilateur fait absolument. Oui, cela produit du code machine… mais ce n'est pas le but et ce n'est pas ce qui est demandé.
Les parenthèses ont en effet disparu: comme cela a été dit, elles ne font pas partie du code machine, qui est un chiffre et pas autre chose. Le code d'assemblage n'est pas un code machine, il est lisible semi-humain et contient les instructions par nom - pas opcode. La machine exécute ce qu'on appelle des opcodes - des représentations numériques du langage d'assemblage.
Des langages comme Java tombent dans une zone intermédiaire car ils ne se compilent que partiellement sur la machine qui les produit. Ils sont compilés pour usiner du code spécifique sur la machine qui les exécute, mais cela ne fait aucune différence pour cette question - les parenthèses ont toujours disparu après la première compilation.
la source
a = f() + (g() + h());
, le compilateur est libre d'appelerf
,g
eth
dans cet ordre (ou dans n'importe quel ordre qui lui plaît).Les compilateurs, quelle que soit la langue, traduisent toutes les mathématiques d'infixe en suffixe. En d'autres termes, lorsque le compilateur voit quelque chose comme:
il le traduit en ceci:
Cela est dû au fait que bien que la notation infixe soit plus facile à lire pour les gens, la notation postfixée est beaucoup plus proche des étapes réelles que l'ordinateur doit prendre pour faire le travail (et parce qu'il existe déjà un algorithme bien développé pour cela.) définition, postfix a éliminé tous les problèmes liés à l'ordre des opérations ou aux parenthèses, ce qui facilite naturellement les choses lors de l'écriture du code machine.
Je recommande l'article wikipedia sur la notation polonaise inversée pour plus d'informations sur le sujet.
la source