Le compilateur C ++ supprime-t-il / optimise-t-il les parenthèses inutiles?

19

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).

Serge
la source
9
Je pense que C # et Java optimiseront cela aussi. Je crois que lorsqu'ils analysent et créent AST, ils suppriment simplement des choses inutiles évidentes comme ça.
Farid Nouri Neshat
5
Tout ce que j'ai lu indique que la compilation JIT donne facilement une course à l'avance pour son argent, de sorte que seul n'est pas un argument très convaincant. Vous évoquez la programmation de jeux - la vraie raison de favoriser la compilation à l'avance est qu'elle est prévisible - avec la compilation JIT, vous ne savez jamais quand le compilateur va démarrer et essayer de commencer à compiler du code. Mais je noterais que la compilation anticipée en code natif ne s'exclut pas mutuellement avec le garbage collection, voir par exemple Standard ML et D. Et j'ai vu des arguments convaincants que le garbage collection est plus efficace ...
Doval
7
... que RAII et les pointeurs intelligents, il s'agit donc davantage de suivre le chemin bien battu (C ++) que le chemin relativement non lu de la programmation de jeux dans ces langages. Je voudrais également noter que s'inquiéter des parenthèses est insensé - je vois d'où vous venez, mais c'est une micro-optimisation ridicule. Le choix des structures de données et des algorithmes dans votre programme dominera certainement les performances, pas de telles banalités.
Doval
6
Hum ... Quel genre d' optimisation attendez-vous exactement? Si vous parlez d'analyse statique, dans la plupart des langues que je connais, elle sera remplacée par le résultat connu statiquement (les implémentations basées sur LLVM l'appliquent même, AFAIK). Si vous parlez d'ordre d'exécution, cela n'a pas d'importance, car c'est la même opération et sans effets secondaires. L'addition a quand même besoin de deux opérandes. Et si vous l'utilisez pour comparer les performances C ++, Java et C #, il semble que vous n'ayez pas une idée claire de ce que sont les optimisations et de leur fonctionnement, vous devriez donc vous concentrer sur l'apprentissage à la place.
Theodoros Chatzigiannakis
5
Je me demande pourquoi a) vous considérez l'expression entre parenthèses plus lisible (pour moi, elle semble juste laide, trompeuse (pourquoi mettent-ils l'accent sur cet ordre particulier? Ne devrait-elle pas être assiciative ici?) Et maladroite) b) pourquoi vous penseriez sans entre parenthèses, il pourrait mieux fonctionner (il est clair que l'analyse des parens est plus facile pour une machine que d'avoir à raisonner sur les fixités de l'opérateur. Comme le dit Marc van Leuwen, cela n'a absolument aucune influence sur le temps d'exécution).
partir

Réponses:

87

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).

Marc van Leeuwen
la source
s / oldes / plus ancien /. Bonne réponse, +1.
David Conrad
23
Avertissement total: "La question n'est pas très bien posée." - Je ne suis pas d'accord, en prenant "bien mis" pour signifier "suggérant clairement le manque de connaissances que le demandeur veut combler". En substance, la question est "d'optimiser pour X, choisir A ou B, et pourquoi? Que se passe-t-il en dessous?", Ce qui au moins me suggère très clairement quel est le manque de connaissances. L'inconvénient de la question, que vous pointez à juste titre et que vous corrigez très bien, est qu'elle repose sur un modèle mental défectueux.
Jonas Kölker
Car 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.
Phil Perry
1
@OrangeDog vrai, c'est dommage Le commentaire de Ben a fait ressortir quelques personnes qui aiment affirmer que les machines virtuelles sont plus rapides que natives.
gbjbaanb
1
@ JonasKölker: Ma phrase d'ouverture fait en fait référence à la question formulée dans le titre: on ne peut pas vraiment répondre à une question de savoir si le compilateur insère ou supprime des parenthèses, car cela est basé sur une conception erronée du fonctionnement des compilateurs. Mais je conviens qu'il est tout à fait clair quel manque de connaissances doit être comblé.
Marc van Leeuwen
46

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.

gbjbaanb
la source
9
Absolument - collez autant de parenthèses que vous le souhaitez et laissez le compilateur faire le travail difficile pour déterminer ce que vous voulez :)
gbjbaanb
23
La véritable programmation @Serge est davantage une question de lisibilité de votre code que de performances. Vous vous détesterez l'année prochaine lorsque vous aurez besoin de déboguer un plantage et que vous n'aurez que du code "optimisé".
monstre à cliquet
1
@ratchetfreak, vous avez raison mais je sais aussi comment commenter mon code. int a = 6; // = (1 + 2) + 3
Serge
20
@Serge Je sais que cela ne tiendra pas après un an de réglages, avec le temps, les commentaires et le code seront désynchronisés et vous vous retrouverez avecint a = 8;// = 2*3 + 5
ratchet freak
21
ou sur www.thedailywtf.com:int five = 7; //HR made us change this to six...
Mooing Duck
23

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!

short int a = 30001, b = 30002, c = 30003;
int d = -a + b + c;    // ok
int d = (-a + b) + c;  // ok, same code
int d = (-a + b + c);  // ok, same code
int d = ((((-a + b)) + c));  // ok, same code
int d = -a + (b + c);  // undefined behaviour, different code

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.

david.pfx
la source
Un comportement indéfini dans la dernière ligne est dû au fait qu'un court signé n'a que 15 bits après le signe, donc une taille maximale de 32 767, non? Dans cet exemple trivial, le compilateur devrait avertir d'un débordement, non? +1 pour un exemple de compteur, dans les deux cas. S'ils étaient des paramètres d'une fonction, vous ne recevriez pas d'avertissement. De plus, s'il apeut vraiment ne pas être signé, mener votre calcul avec -a + bpourrait facilement déborder également s'il aétait négatif et bpositif.
Patrick M
@PatrickM: voir modifier. Un comportement indéfini signifie que le compilateur peut faire ce qu'il veut, y compris émettre un avertissement ou non. L'arithmétique non signée ne produit pas d'UB, mais est réduite modulo à la puissance supérieure suivante de deux.
david.pfx
L'expression (b+c)dans la dernière ligne va promouvoir ses arguments int, donc à moins que le compilateur ne définisse intcomme 16 bits (soit parce qu'il est ancien ou cible un petit microcontrôleur), la dernière ligne serait parfaitement légitime.
supercat
@supercat: Je ne pense pas. Le type commun et le type du résultat doivent être courts int. Si ce n'est jamais quelque chose qui vous a été demandé, vous aimeriez peut-être poser une question?
david.pfx
@ david.pfx: Les règles des promotions arithmétiques C sont assez claires: tout ce qui est plus petit que celui qui intest promu à intmoins que ce type ne soit pas en mesure de représenter toutes ses valeurs, auquel cas il est promu unsigned 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. Si intun type 16 bits était piégé par le débordement, cependant, il y aurait des cas où l'une des expressions ...
supercat
7

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 ...

  • "int a = ((1 + 2) + 3);"

... un compilateur peut émettre quelque chose de plus comme ceci:

  • Caractère [1] :: "a"
  • Int32 :: DeclareStackVariable ()
  • Int32 :: 0x00000001
  • Int32 :: 0x00000002
  • Int32 :: Add ()
  • Int32 :: 0x00000003
  • Int32 :: Add ()
  • Int32 :: AssignToVariable ()
  • void :: DiscardResult ()

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!

Phill W.
la source
4
Il n'y a pas un seul compilateur C ++ qui produira quoi que ce soit même à distance comme ça. Généralement, ils produisent du code CPU réel, et l'assemblage ne le regarde pas non plus.
MSalters
3
l'intention était de démontrer la différence de structure entre le code tel qu'il est écrit et la sortie compilée. même ici, la plupart des gens ne pourraient pas lire le code ou l'assemblage réel de la machine
DHall
@MSalters Nitpicking clang émettrait `` quelque chose comme ça '' si vous traitez LLVM ISA comme `` quelque chose comme ça '' (c'est SSA non basé sur la pile). Étant donné qu'il est possible d'écrire un backend JVM pour LLVM et JVM ISA (AFAIK), la pile basée sur clang-> llvm-> JVM serait très similaire.
Maciej Piechotka
Je ne pense pas que le LLVM ISA a la capacité de définir des variables de pile de noms en utilisant des littéraux de chaîne d'exécution (juste les deux premières instructions). Cela mélange sérieusement le temps d'exécution et le temps de compilation. La distinction est importante parce que cette question concerne exactement cette confusion.
MSalters
6

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.

monstre à cliquet
la source
+1 pour avoir indiqué que les opérations en virgule flottante ne sont pas associatives.
Doval
Dans l'exemple de la question, les parenthèses ne modifient pas l'associativité par défaut (gauche), donc ce point est théorique.
Marc van Leeuwen
1
À propos de la dernière phrase, je ne pense pas. Dans java a et c #, le compilateur produira un bytecode / IL optimisé. Le temps d'exécution n'est pas affecté.
Stefano Altieri
IL ne fonctionne pas sur les expressions de ce type, les instructions prennent un certain nombre de valeurs d'une pile et renvoient un certain nombre de valeurs (généralement 0 ou 1) à la pile. Parler de ce genre de chose qui est optimisé au moment de l'exécution en C # est un non-sens.
Jon Hanna
6

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.

The Spooniest
la source
5

Les deux codes sont codés en dur 6:

movl    $6, -4(%rbp)

Vérifiez par vous-même ici

MonoThreaded
la source
2
Qu'est-ce que ce truc assembly.ynh.io ?
Peter Mortensen
C'est un pseudo-compilateur qui transforme le code C en assembleur x86 en ligne
MonoThreaded
1

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:

int a = 6;

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être 6, 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:

int a = (b - 2) / 2;
/* or */
int a = (b / 2)--;

et

int c;
if(d < 100)
  c = 0;
else
  c = d * 31;
/* or */
int c = d < 100 ? 0 : d * 32 - d
/* or */
int c = d < 100 && d * 32 - d;
/* or */
int c = (d < 100) * (d * 32 - d);

(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 * 32pourrait peut-être être produit plus rapidement en le transformant en d << 5rendant ainsi d * 32 - dplus 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).

Cela peut sembler être une toute petite préoccupation d'optimisation,

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) + 3pourrait être plus lent que le plus facile à lire 1 + 2 + 3.

mais choisir C ++ plutôt que C # / Java / ... est une question d'optimisations (IMHO).

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:

  1. C'est maintenant plus rapide pour un cas d'utilisation, mais plus lent pour un autre.
  2. C'est maintenant plus rapide, mais utilise plus de mémoire.
  3. Il est maintenant plus léger en mémoire, mais plus lent.
  4. C'est maintenant plus rapide, mais plus difficile à entretenir.
  5. Il est maintenant plus facile à entretenir, mais plus lent.

De tels cas seraient justifiés si, par exemple:

  1. Le cas d'utilisation plus rapide est plus courant ou plus gravement entravé au départ.
  2. Le programme était trop lent et nous avons beaucoup de RAM libre.
  3. Le programme était à l'arrêt car il utilisait tellement de RAM qu'il passait plus de temps à échanger qu'à exécuter son traitement ultra-rapide.
  4. Le programme était trop lent et le code plus difficile à comprendre est bien documenté et relativement stable.
  5. Le programme est toujours assez rapide, et la base de code plus compréhensible est moins chère à entretenir et permet d'autres améliorations à apporter plus facilement.

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.

Jon Hanna
la source
0

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

int a = 1 + 2 + 3;

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

int a = 1 + (2 + 3);

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

int a = 10 - (5 - 4);

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 .

gnasher729
la source
practically every language in existence; sauf APL: Essayez (ici) [tryapl.org] en entrant (1-2)+3(2), 1-(2+3)(-4) et 1-2+3(également -4).
tomsmeding
0

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.

jinzai
la source
1
Je ne suis pas sûr que cela réponde à la question. Les paragraphes à l'appui sont plus déroutants qu'utiles. En quoi le compilateur Java est-il pertinent pour le compilateur C ++?
Adam Zuckerman
OP a demandé si les parenthèses avaient disparu… J'ai répondu par l'affirmative et j'ai expliqué que le code exécutable n'était que des nombres représentant des opcodes. Java a été évoqué dans une autre réponse. Je pense que cela répond très bien à la question… mais ce n'est que mon avis. Merci d'avoir répondu.
jinzai
3
Les parenthèses ne forcent pas "l'ordre de fonctionnement". Ils changent de priorité. Donc, dans a = f() + (g() + h());, le compilateur est libre d'appeler f, get hdans cet ordre (ou dans n'importe quel ordre qui lui plaît).
Alok
Je suis en désaccord avec cette affirmation… vous pouvez absolument contraindre l'ordre de fonctionnement avec des parenthèses.
jinzai
0

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:

((a+b)+c)

il le traduit en ceci:

 a b + c +

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.

Brian Drozd
la source
5
C'est une hypothèse incorrecte sur la façon dont les compilateurs traduisent les opérations. Vous supposez par exemple une machine de pile ici. Et si vous aviez un processeur vectoriel? Et si vous aviez une machine avec un grand nombre de registres?
Ahmed Masud