Pourquoi l'optimisation est-elle souvent désactivée dans un programme C / C ++ en mode débogage?

StackOverflow https://stackoverflow.com/questions/69250

  •  09-06-2019
  •  | 
  •  

Question

Dans la plupart des environnements C ou C ++, il existe un "débogage". mode et un "communiqué de presse" mode compilation.
En regardant la différence entre les deux, vous constaterez que le mode débogage ajoute les symboles de débogage (souvent l'option -g sur de nombreux compilateurs), mais il désactive également la plupart des optimisations.
Dans " release " mode, toutes sortes d’optimisations sont activées.
Pourquoi cette différence?

Était-ce utile?

La solution

Sans optimisation, le flux de votre code est linéaire. Si vous êtes sur la ligne 5 et pas à pas unique, vous passez à la ligne 6. L'optimisation étant activée, vous pouvez obtenir un nouvel ordre des instructions, un déroulement en boucle et toutes sortes d'optimisations.
Par exemple:


void foo() {
1:  int i;
2:  for(i = 0; i < 2; )
3:    i++;
4:  return;

Dans cet exemple, sans optimisation, vous pouvez parcourir le code d’un seul coup et toucher les lignes 1, 2, 3, 2, 3, 2, 4

Lorsque l'optimisation est activée, vous pouvez obtenir un chemin d'exécution ressemblant à: 2, 3, 3, 4 ou même tout simplement 4! (La fonction ne fait rien après tout ...)

En bout de ligne, le code de débogage avec optimisation activée peut être une douleur royale! Surtout si vous avez de grandes fonctions.

Notez que l'activation de l'optimisation change le code! Dans certains environnements (systèmes critiques de sécurité), ceci est inacceptable et le code en cours de débogage doit être le code fourni. Il faut déboguer avec optimisation dans ce cas.

Alors que le code optimisé et non optimisé doit être "fonctionnellement" équivalent, dans certaines circonstances, le comportement va changer.
Voici un exemple simpliste:

    int* ptr = 0xdeadbeef;  // some address to memory-mapped I/O device
    *ptr = 0;   // setup hardware device
    while(*ptr == 1) {    // loop until hardware device is done
       // do something
    }

Lorsque l’optimisation est désactivée, la procédure est simple et vous savez à quoi vous attendre. Toutefois, si vous activez l’optimisation, plusieurs événements peuvent se produire:

  • Le compilateur pourrait optimiser le bloc while (nous commençons à 0, il ne sera jamais 1)
  • Au lieu d'accéder à la mémoire, l'accès au pointeur peut être déplacé vers un registre -> Aucune mise à jour d'E / S
  • l'accès mémoire peut être mis en cache (pas nécessairement lié à l'optimisation du compilateur)

Dans tous ces cas, le comportement serait radicalement différent et probablement erroné.

Autres conseils

Une autre différence cruciale entre le débogage et la publication concerne la façon dont les variables locales sont stockées. Conceptuellement, les variables locales se voient allouer de la mémoire dans un cadre de pile de fonctions. Le fichier de symboles généré par le compilateur indique au débogueur le décalage de la variable dans le cadre de la pile afin que le débogueur puisse vous l'afficher. Le débogueur jette un coup d’œil à l’emplacement mémoire pour le faire.

Toutefois, cela signifie que chaque fois qu’une variable locale est modifiée, le code généré pour cette ligne source doit écrire la valeur à l’emplacement correct de la pile. Ceci est très inefficace en raison de la surcharge de mémoire.

Dans une version finale, le compilateur peut affecter une variable locale à un registre pour une partie d'une fonction. Dans certains cas, il se peut que la pile de stockage ne lui soit pas du tout attribuée (plus il y a de registres, plus c'est facile à faire).

Cependant, le débogueur ne sait pas comment les registres correspondent aux variables locales pour un point particulier du code (je ne connais aucun format de symbole contenant cette information), il ne peut donc pas vous le montrer avec précision. car il ne sait pas où aller le chercher.

Une autre optimisation serait la fonction en ligne. Dans les versions optimisées, le compilateur peut remplacer un appel à foo () par le code réel de foo partout où il est utilisé, car la fonction est suffisamment petite. Cependant, lorsque vous essayez de définir un point d'arrêt sur foo (), le débogueur veut connaître l'adresse des instructions pour foo () et il n'y a plus de réponse simple à cela - il peut y avoir des milliers de copies du foo ( ) octets de code répartis sur votre programme. Une version de débogage garantira qu’il ya un endroit où placer le point d’arrêt.

L'optimisation du code est un processus automatisé qui améliore les performances d'exécution du code tout en préservant la sémantique. Ce processus peut supprimer les résultats intermédiaires inutiles pour compléter une évaluation d'expression ou de fonction, mais peut vous intéresser lors du débogage. De même, les optimisations peuvent modifier le flux de contrôle apparent de sorte que les choses puissent se passer dans un ordre légèrement différent de celui qui apparaît dans le code source. Ceci est fait pour ignorer les calculs inutiles ou redondants. Ce réaménagement du code peut perturber le mappage entre les numéros de ligne de code source et les adresses de code objet, ce qui empêche un débogueur de suivre le flux de contrôle tel que vous l'avez écrit.

Le débogage en mode non optimisé vous permet de voir tout ce que vous avez écrit tel que vous l'avez écrit sans que l'optimiseur ne supprime ou ne réorganise les choses.

Une fois que vous êtes satisfait du bon fonctionnement de votre programme, vous pouvez activer les optimisations pour améliorer les performances. Même si les optimiseurs sont assez fiables de nos jours, il est toujours judicieux de créer une suite de tests de bonne qualité pour garantir que votre programme fonctionne de manière identique (d’un point de vue fonctionnel, sans prendre en compte les performances) en mode optimisé et non optimisé.

On s’attend à ce que la version de débogage soit - déboguée! La définition de points d'arrêt, la progression pas à pas tout en regardant les variables, les traces de pile et tout ce que vous faites dans un débogueur (IDE ou autre) ont du sens si chaque ligne de code source non vide ni commentaire correspond à une instruction de code machine.

La plupart des optimisations dérangent l'ordre des codes machine. Le déroulement de la boucle est un bon exemple. Les sous-expressions courantes peuvent être extraites de boucles. Lorsque l'optimisation est activée, même au niveau le plus simple, vous essayez peut-être de définir un point d'arrêt sur une ligne qui, au niveau du code machine, n'existe pas. Parfois, vous ne pouvez pas surveiller une variable locale car elle est conservée dans un registre de la CPU, voire optimisée, sans existence!

Si vous déboguez au niveau instruction plutôt qu'au niveau source, il est beaucoup plus facile pour vous de mapper des instructions non optimisées vers la source. De plus, les compilateurs sont parfois bogués dans leurs optimiseurs.

Dans la division Windows de Microsoft, tous les fichiers binaires de la version sont construits avec des symboles de débogage et des optimisations complètes. Les symboles sont stockés dans des fichiers PDB distincts et n’affectent pas les performances du code. Ils ne sont pas livrés avec le produit, mais la plupart d’entre eux sont disponibles sur le Microsoft Symbol Server . .

Un autre problème lié aux optimisations concerne les fonctions en ligne, en ce sens que vous les parcourrez toujours en une seule étape.

Avec GCC, avec le débogage et les optimisations activés ensemble, si vous ne savez pas à quoi vous attendre, vous penserez que le code se comporte de manière incorrecte et qu'il réexécute la même instruction plusieurs fois - cela est arrivé à quelques collègues. De plus, les informations de débogage données par GCC avec optimisations ont tendance à être de moins bonne qualité qu’elles ne pourraient le faire.

Toutefois, dans les langages hébergés par une machine virtuelle telle que Java, optimisations et débogage peuvent coexister - même pendant le débogage, la compilation JIT en code natif se poursuit et seul le code des méthodes de débogage est converti de manière transparente en une version non optimisée.

Je tiens à souligner que l'optimisation ne doit pas modifier le comportement du code, sauf si l'optimiseur utilisé est bogué ou si le code lui-même est bogué et repose sur une sémantique partiellement non définie; ce dernier est plus courant dans la programmation multithread ou lorsqu'un assemblage en ligne est également utilisé.

  

Les codes avec les symboles de débogage sont plus gros, ce qui peut signifier davantage d’oubli de mémoire cache, c’est-à-dire plus lent, ce qui peut poser problème pour le logiciel serveur.

Du moins sous Linux (et il n’ya aucune raison pour que Windows soit différent), les informations de débogage sont regroupées dans une section distincte du fichier binaire et ne sont pas chargées lors de l’exécution normale. Ils peuvent être divisés en un fichier différent à utiliser pour le débogage. En outre, sur certains compilateurs (y compris Gcc, je suppose également avec le compilateur C de Microsoft), les informations de débogage et les optimisations peuvent être activées ensemble. Sinon, le code sera évidemment plus lent.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top