10
votes

Quand les optimisations peuvent-elles effectuées par le compilateur détruire mon code C ++?

Lorsque les optimisations apportées par le compilateur peuvent-elles faire valoir que mon code C ++ présente un comportement incorrect qui ne serait pas présent si ces optimisations n'ont pas été effectuées? Par exemple, n'utilisez pas volatile dans certaines circonstances peut entraîner un comportement de manière incorrecte (par exemple, ne pas relecture de la valeur d'une variable de la mémoire et ne le lit que une fois et le stocke en inscrit). Mais existe-t-il d'autres pièges que l'on devrait savoir avant d'allumer le drapeau d'optimisation le plus agressif et se demandant ensuite pourquoi le programme ne fonctionne plus?


6 commentaires

Demandez-vous des bugs dans votre programme ou vos bogues dans le compilateur?


Eh bien, je supposerais que le bogue qui se produit due à des optimisations est situé dans le programme, pas le compilateur. À moins que l'optimisation ne soit effectivement complètement fausse, dans laquelle le bogue réside dans le compilateur. Donc, pour y rephérer un peu différemment; Quels bugs dans mon programme peuvent survenir pour faire des optimisations du compilateur?


Indépendamment si l'optimisation allumée ou éteint le compilateur doit produire du code selon la norme C ++. Tous les autres cas sont des bogues dans le programme ou dans le compilateur.


Pas vraiment une optimisation, mais vous devriez faire attention à ne pas avoir de code avec des effets secondaires dans les affirmations, sinon le comportement peut changer lorsque des affirmations sont élues en libération (en supposant qu'elles sont élues en libération)


@JK: Ah, n'avait pas pensé à cela auparavant. Bonne.


Les optimisations effectuées sur le code par le compilateur ne peuvent pas détruire le code mais peuvent limiter gravement votre capacité à faire du débogage de niveau source.


11 Réponses :


5
votes

Outre le cas que vous avez mentionné, la timing peut modifier le code multi-threadé de telle sorte que ce qui semble fonctionner ne fonctionne plus. La mise en place de variables locales peut varier de manière à ce que des comportements nocifs comme un dépassement de mémoire tampon de mémoire se produisent dans le débogage mais non libérés, optimisés ou non optimisés, ou inversement. Mais tous ces sont déjà des bugs qui y étaient déjà, viennent d'être exposés par des changements d'option du compilateur.

Cela suppose que le compilateur n'a pas de bugs dans son optimiseur.


0 commentaires

3
votes

Je ne l'ai rencontré que avec des mathématiques à virgule flottante. Parfois, les optimisations de vitesse peuvent changer la réponse un peu. Bien sûr, avec des mathématiques de point flottant, la définition de «droite» n'est pas toujours facile à trouver, vous devez donc exécuter des tests et voir si les optimisations font ce que vous attendez. Les optimisations ne rendent pas nécessairement le franc, juste différent.

Autre que cela, je n'ai jamais vu d'optimisations cassez le code correct. Les écrivains compilateurs sont assez intelligents et savent ce qu'ils font.


0 commentaires

20
votes

Les optimisations du compilateur ne doivent pas affecter le comportement observable de votre programme, donc en théorie, vous n'avez pas besoin de vous inquiéter. En pratique, si votre programme s'arrise d'un comportement non défini, tout pourrait déjà se produire, de sorte que votre programme se casse lorsque vous activez des optimisations, vous avez simplement exposé des bugs existants - ce n'était pas l'optimisation qui l'a cassé. .

Un point d'optimisation commune est l'optimisation de la valeur de retour (RVO) et l'optimisation de la valeur de retour nommée (NRVO) qui signifie fondamentalement que les objets renvoyés par la valeur des fonctions sont construits directement dans l'objet qui les reçoit, plutôt que de faire une copie. Cela ajuste la commande et le nombre de constructeurs, constructeur de copie et destructeurs destructeurs - mais généralement avec ces fonctions correctement écrites, il n'y a toujours aucune différence observable dans le comportement.


3 commentaires

@gablin: oui. Bien que C ++ 0x spécifie cela dans la norme plus clairement, je crois. C ++ manque de bonnes normes de filetage. Mais optimiser les compilateurs sont conçus pour ne rien faire d'observable, sauf rendre les choses plus rapides / utilisent moins de mémoire / être plus efficace.


J'essaie de ne pas devoir que quelqu'un ou quoi que ce soit. Si mon compilateur peut offrir une optimisation dangereuse lorsque certaines conditions sont remplies pour améliorer considérablement les performances lorsque ces conditions ne sont pas remplies, je souhaite que mon compilateur offre ces optimisations. Je m'attends à ce que les optimisations dangereuses soient désactivées par défaut et que le compilateur documentera de manière adéquate les "certaines conditions" afin que je puisse prendre une décision éclairée sur la mise en marche des optimisations.


Je pense que ce sujet a été assez couvert d'ici. Toutes les réponses ont été beaucoup d'usage, merci à tout le monde! Je vais mettre cela comme accepté car il a eu le plus de votes et couvre presque tout le cœur de tout.



1
votes

Je viens de voir récemment cela (en C ++ 0x), le compilateur est autorisé à supposer que certaines classes de boucles se terminent toujours (pour permettre des optimisations). Je ne trouve pas la référence en ce moment mais je vais essayer de le lier si je peux le suivre. Cela peut entraîner des changements de programme observables.


5 commentaires

J'aimerais voir si quelqu'un peut fournir un exemple pratique là où cela affecterait le comportement dans un système réel.


... autorisé à supposer que une certaine classe limitée de boucles se termine toujours ...


Stackoverflow.com/Questtions/3592557/... < / a> @Tenfour: Il existe quelques exemples dans les articles liés impliquant des systèmes intégrés dans lesquels des boucles infinies sont légitimement utiles, mais elles semblent une portée assez limitée.


@Tenfour: Exemple ici, blog.regehr.org/archives/161 . Fondamentalement, un programme qui recherche un contre-exemple pour le dernier théorème de Fermat, ce n'est pas surtout de code contourné. La plupart des gens qui lisent, il penserait qu'il ne se termine jamais: le théorème est vrai. En fait, il a un comportement non défini, car il contient une boucle que le compilateur est autorisé à assumer des terminaux, mais qui, sinon pour cette clause obscure dans la norme, ne terminerait certainement pas. Avec le compilateur d'Intel, sous Windows, il se termine indiquant que le dernier théorème de Fermat est faux.


Fondamentalement, les compilateurs sont autorisés à déplacer le code qui n'exécuterait qu'après une ligne de boucle terminée, de sorte que ce dernier code exécutera avant ou pendant l'exécution de la boucle, à condition que, dans l'événement, la boucle se termine - la séquence observable des événements ne serait pas affecté par la réécriture (même si leur timing pourrait être).



0
votes

Je n'ai pas les détails exacts (peut-être que quelqu'un d'autre peut choisir), mais j'ai entendu parler d'un bug causé par une boucle déroulante / optimisation si la variable de compteur de boucle est de type char / uint8_t (dans un contexte GCC, c'est-à-dire ).


0 commentaires

1
votes

Un aliasing strict est un problème que vous pourriez rencontrer avec GCC. D'après ce que je comprends, avec certaines versions de GCC (GCC 4.4), il est automatiquement activé avec des optimisations. Ce site http://cellperformance.beyond3d.com/articles/ 2006/06 / compréhension-strict-aliasing.html fait un très bon travail pour expliquer des règles d'aliasing strictes.


1 commentaires

Oui, je dois toujours spécifier -fno-strict-aliasing pour mon VMS et d'autres cadres qui font des choses semi-atypiques avec la mémoire ou bien ils rompent sur Linux et Mac.



2
votes

Ne travaillez pas de l'hypothèse que l'optimiseur jamais détruit votre code. Ce n'est tout simplement pas ce qu'il a été fait à faire. Si vous observent des problèmes, envisagez automatiquement une ub intentionnelle.

Oui, le filetage peut jouer au ravage avec le type d'hypothèses que vous êtes habitué. Vous n'obtenez aucune aide de la langue ni du compilateur, bien que cela change. Ce que vous faites à ce sujet, c'est pas de pisse en pisse avec volatile, vous utilisez une bonne bibliothèque de threading. Et vous utilisez l'une de ses primitives de synchronisation partout où deux ou plusieurs threads peuvent tous les deux toucher des variables. En essayant de prendre des coupes courtes ou d'optimiser cela vous-même est un billet aller simple en enfer.


6 commentaires

Ne commencez pas en supposant que le compilateur ou l'optimiseur est cassé, mais ne laissez pas toujours de côté cette possibilité. Souvent, je trouve examiner la sortie du compilateur pour certains codes qui se comportent étrangement est utile. À des occasions rares, il est exposé des bugs de compilation; Dans de nombreuses occasions, il a révélé comment le compilateur interprète ce que j'ai écrit (des choses comme des interactions entre les types signés et non signés peuvent être délicats).


J'ai du mal à comprendre votre première ligne: voulez-vous dire que je ne devrais pas supposer que le compilateur toujours détruit mon code, ou que jamais détruit mon code? Et que signifie "UB"? Sur une note complètement différente, je pense que volatile me hantera pour toujours après avoir demandé à ces questions précédentes. ^^ Un meilleur nom pour ce mot clé aurait été violé , car c'est comme ça que je ressens après l'avoir utilisé.


@Gablin: Il dit commencer par regarder votre code pour des bugs; Supposons toujours que le compilateur est correct jusqu'à ce que vous soyez sûr que votre propre code n'est pas la cause des problèmes que vous avez témoin.


@Gablin: D'accord avec Dennis. Ub = comportement non défini. Oui, ce n'était probablement pas une bonne idée d'élever volatile , il suffit de mentionner le threading la prochaine fois, je suppose. Vous chassez un fantôme volatil, il est rarement vu.


Un commentaire de blague, mais aussi sérieux: lorsque j'ai commencé à programmer sur UNIX, le compilateur a détruit mon code. Non, vraiment ça fait! Lors de la numérisation du programme de programme CC -O.c sur la ligne de commande, il est terriblement facile pour l'achèvement de l'onglet Shell pour faire ce programme CC -O.C.C.C


@Hans Passant: Ouais, mais au moins, j'ai eu pour en savoir plus à ce sujet. Donc, d'une manière ou d'une autre, cela en valait la peine. ^^ merci.



1
votes

Au niveau méta, si votre code utilise des comptes sur un comportement basé sur des aspects indéfinis de la norme C ++, un compilateur conforme aux normes est libre de détruire votre code C ++ (comme vous le mettez). Si vous n'avez pas de compilateur conformément aux normes, il peut également faire des choses non standard, comme détruire votre code de toute façon.

La plupart des compilateurs publient ce que le sous-ensemble de la norme C ++ est conforme. Vous pouvez donc toujours écrire votre code à cette norme particulière et supposez surtout que vous êtes en sécurité. Cependant, vous ne pouvez pas vraiment vous garder contre des insectes dans le compilateur sans les avoir rencontrés en premier lieu, vous ne vous êtes donc toujours pas vraiment garanti.


0 commentaires

2
votes

Ne pas inclure le mot clé volatile lors de la déclaration d'accès à un emplacement de mémoire volatile ou à un périphérique IO est un bogue dans votre code; Même si le bug n'est évident que lorsque votre code est optimisé.

Votre compilateur documentera toutes les optimisations «dangereuses» où il documente les commutateurs de ligne et les pragmas qui les allument et éteignent. Les optimisations dangereuses sont généralement liées à des hypothèses sur les mathématiques ponctuelles flottantes (arrondie, étuis de bord tels que NAN) ou aliasing, comme d'autres personnes déjà mentionnées.

Pliage constant peut créer des bugs d'aliasing dans votre code apparaissent. Donc, par exemple, si vous avez du code comme: xxx

Votre code est essentiellement une erreur dans laquelle vous griffonnez sur une constante (littéral). Sans pliage constant, l'erreur n'aura aucun effet vraiment. Mais beaucoup comme le bug volatile que vous avez mentionné, lorsque votre compilateur plie des constantes pour économiser de l'espace, vous pouvez gribouiller sur un autre littéral comme les espaces de: xxx

car le compilateur pourrait pointer le Argument littéral à l'appel Printf au cours des 4 derniers caractères du littéral utilisé pour initialiser Cabuffer.


0 commentaires

3
votes

Les bugs causés par des optimisations de compilateur qui ne sont pas enracinés dans des bugs dans votre code ne sont pas prévisibles et difficiles à déterminer (j'ai réussi à en trouver une fois lors de l'examen du code de montage, un compilateur avait créé lors de l'optimisation d'une certaine zone de mon code une fois) . L'affaire commune est que si une optimisation rend votre programme instable, il révèle simplement une faille dans votre programme.


0 commentaires

2
votes

Tant que votre code ne s'appuie pas sur des manifestations spécifiques de comportement indéfini / non spécifié, et tant que la fonctionnalité de votre code est définie en termes de comportement observable du programme C ++, un compilateur C ++ Les optimisations ne peuvent pas détruisent éventuellement la fonctionnalité de votre code avec une seule exception :

  • Lorsqu'un objet temporaire est créé avec le seul but d'être immédiatement copié et détruit, le compilateur est autorisé à éliminer la création d'un tel objet temporaire, même si le constructeur / destructeur de l'objet a des effets secondaires affectant la comportement observable du programme.

    Dans les nouvelles versions plus récentes de la norme C ++ que la permission est étendue pour couvrir l'objet nommé dans l'optimisation de la valeur de retour nommée (NRVO).

    C'est la seule façon dont les optimisations peuvent détruire la fonctionnalité du code C ++ conforme. Si votre code souffre d'optimisations d'une autre manière, c'est un bogue dans votre code ou un bogue dans le compilateur.

    On peut dire cependant que ce qui s'appuie sur ce comportement n'est en fait rien d'autre que de s'appuyer sur une manifestation spécifique du comportement non spécifié . Il s'agit d'un argument valide, qui peut être utilisé pour supporter l'affirmation selon laquelle les optimisations de conditions ci-dessus peuvent jamais casser la fonctionnalité du programme.

    Votre exemple original avec volatile n'est pas un exemple valide. Vous accusez essentiellement le compilateur des garanties de casse qui n'existaient jamais en premier lieu. Si votre question doit être interprétée de cette manière spécifique (c'est-à-dire ce que de fausses garanties imaginaires aléatoires non existantes peut-être optimiseur éventuellement casser), le nombre de réponses possibles est pratiquement infinie. La question n'aurait tout simplement pas beaucoup de sens.


0 commentaires