Différences de performances entre les versions de débogage et de version


280

Je dois admettre que, généralement, je n'ai pas pris la peine de basculer entre les configurations de débogage et de version dans mon programme, et j'ai généralement opté pour la configuration de débogage , même lorsque les programmes sont effectivement déployés chez le client.

Pour autant que je sache, la seule différence entre ces configurations si vous ne la modifiez pas manuellement est que Debug a la DEBUGconstante définie et Release a vérifié le code Optimize .

Donc, mes questions sont en fait doubles:

  1. Existe-t-il de grandes différences de performances entre ces deux configurations? Existe-t-il un type de code spécifique qui entraînera de grandes différences de performances ici, ou n'est-ce pas vraiment si important?

  2. Existe-t-il un type de code qui fonctionnera correctement sous la configuration de débogage qui pourrait échouer sous la configuration de version , ou pouvez-vous être certain que le code testé et fonctionnant correctement sous la configuration de débogage fonctionnera également correctement sous la configuration de version.


Réponses:


511

Le compilateur C # lui-même n'altère pas beaucoup l'IL émis dans la version Release. Il convient de noter qu'il n'émet plus les opcodes NOP qui vous permettent de définir un point d'arrêt sur une accolade. Le gros est l'optimiseur intégré au compilateur JIT. Je sais que cela apporte les optimisations suivantes:

  • Intégration de la méthode. Un appel de méthode est remplacé par l'injection du code de la méthode. C'est un gros problème, cela rend les accesseurs de propriété essentiellement gratuits.

  • Allocation de registre CPU. Les variables locales et les arguments de méthode peuvent rester stockés dans un registre CPU sans jamais (ou moins fréquemment) être stockés dans le cadre de la pile. C'est un gros problème, remarquable pour rendre le débogage du code optimisé si difficile. Et donner un sens au mot clé volatile .

  • Élimination de la vérification de l'index du tableau. Une optimisation importante lorsque vous travaillez avec des tableaux (toutes les classes de collection .NET utilisent un tableau en interne). Lorsque le compilateur JIT peut vérifier qu'une boucle n'indexe jamais un tableau hors limites, il élimine la vérification d'index. Un gros.

  • Déroulement de la boucle. Les boucles avec de petits corps sont améliorées en répétant le code jusqu'à 4 fois dans le corps et en bouclant moins. Réduit le coût de la branche et améliore les options d'exécution super-scalaire du processeur.

  • Élimination du code mort. Une instruction comme if (false) {/ ... /} est complètement éliminée. Cela peut se produire en raison d'un pliage et d'une doublure constants. Dans d'autres cas, le compilateur JIT peut déterminer que le code n'a aucun effet secondaire possible. Cette optimisation est ce qui rend le profilage du code si délicat.

  • Levage de code. Le code à l'intérieur d'une boucle qui n'est pas affectée par la boucle peut être déplacé hors de la boucle. L'optimiseur d'un compilateur C passera beaucoup plus de temps à trouver des opportunités de hissage. Il s'agit cependant d'une optimisation coûteuse en raison de l'analyse de flux de données requise et la gigue ne peut pas se permettre le temps, donc ne soulève que les cas évidents. Forcer les programmeurs .NET à écrire un meilleur code source et à se hisser.

  • Élimination de la sous-expression commune. x = y + 4; z = y + 4; devient z = x; Assez commun dans les instructions comme dest [ix + 1] = src [ix + 1]; écrit pour la lisibilité sans introduire de variable d'assistance. Pas besoin de compromettre la lisibilité.

  • Pliage constant. x = 1 + 2; devient x = 3; Cet exemple simple est détecté tôt par le compilateur, mais se produit au moment JIT lorsque d'autres optimisations rendent cela possible.

  • Propagation de copie. x = a; y = x; devient y = a; Cela aide l'allocateur de registre à prendre de meilleures décisions. C'est un gros problème dans la gigue x86 car il a peu de registres avec lesquels travailler. Le faire sélectionner les bons est essentiel à la performance.

Ce sont des optimisations très importantes qui peuvent faire une grande différence lorsque, par exemple, vous profilez la version Debug de votre application et la comparez à la version Release. Cela n'a vraiment d'importance que lorsque le code se trouve sur votre chemin critique, les 5 à 10% du code que vous écrivez affectent réellement la performance de votre programme. L'optimiseur JIT n'est pas assez intelligent pour savoir à l'avance ce qui est critique, il ne peut appliquer que le cadran "tourner à onze" pour tout le code.

Le résultat effectif de ces optimisations sur le temps d'exécution de votre programme est souvent affecté par du code qui s'exécute ailleurs. Lecture d'un fichier, exécution d'une requête dbase, etc. Rendre le travail optimiseur JIT complètement invisible. Cela ne me dérange pas cependant :)

L'optimiseur JIT est un code assez fiable, principalement parce qu'il a été testé des millions de fois. Il est extrêmement rare de rencontrer des problèmes dans la version Release build de votre programme. Cela arrive cependant. Les tremblements x64 et x86 ont tous deux eu des problèmes avec les structures. La gigue x86 a des problèmes de cohérence en virgule flottante, produisant des résultats subtilement différents lorsque les intermédiaires d'un calcul en virgule flottante sont conservés dans un registre FPU avec une précision de 80 bits au lieu d'être tronqués lorsqu'ils sont vidés en mémoire.


23
Je ne pense pas que toutes les collections utilisent des tableaux: LinkedList<T>non, même si elles ne sont pas utilisées très souvent.
svick

Je pense que le CLR configure le FPU avec une précision de 53 bits (correspondant aux doubles larges de 64 bits), il ne devrait donc pas y avoir de calculs doubles étendus de 80 bits pour les valeurs Float64. Cependant, les calculs Float32 peuvent être calculés avec cette précision de 53 bits et tronqués uniquement lorsqu'ils sont stockés en mémoire.
Govert

2
Le volatilemot clé ne s'applique pas aux variables locales stockées dans un cadre de pile. De la documentation à msdn.microsoft.com/en-us/library/x13ttww7.aspx : "Le mot clé volatile ne peut être appliqué qu'aux champs d'une classe ou d'une structure. Les variables locales ne peuvent pas être déclarées volatiles."
Kris Vandermotten

8
en tant qu'amendement humble, je suppose que ce qui fait vraiment la différence entre Debuget Releaseconstruit à cet égard est la case à cocher "optimiser le code" qui est normalement activée pour Releasemais désactivée pour Debug. C'est juste pour s'assurer que les lecteurs ne commencent pas à penser qu'il existe des différences "magiques" et invisibles entre les deux configurations de build qui vont au-delà de ce qui se trouve sur la page de propriétés du projet dans Visual Studio.
chiccodoro

3
Il convient peut-être de mentionner que pratiquement aucune des méthodes de System.Diagnostics.Debug ne fait quoi que ce soit dans une version de débogage. De plus, les variables ne sont pas finalisées assez rapidement (voir stackoverflow.com/a/7165380/20553 ).
Martin Brown du

23
  1. Oui, il existe de nombreuses différences de performances et elles s'appliquent vraiment à tout votre code. Le débogage n'optimise que très peu les performances et le mode de libération est très important;

  2. Seul le code qui repose sur le DEBUG constante peut fonctionner différemment avec une version de version. En plus de cela, vous ne devriez voir aucun problème.

Un exemple de code cadre qui dépend de la DEBUGconstante est la Debug.Assert()méthode, dont l'attribut est [Conditional("DEBUG)"]défini. Cela signifie que cela dépend également de la DEBUGconstante et que cela n'est pas inclus dans la version.


2
Tout cela est vrai, mais pourriez-vous jamais mesurer une différence? Ou remarquez une différence lors de l'utilisation d'un programme? Bien sûr, je ne veux encourager personne à publier son logiciel en mode débogage, mais la question était de savoir s'il y avait une énorme différence de performances et je ne peux pas le voir.
testalino

2
Il convient également de noter que les versions de débogage sont en corrélation avec le code source d'origine à un degré beaucoup plus élevé que les versions de sortie. Si vous pensez (même si cela est peu probable) que quelqu'un pourrait essayer de désosser vos exécutables, vous ne voulez pas leur faciliter la tâche en déployant des versions de débogage.
jwheron

2
@testalino - Eh bien, ces jours-ci, c'est difficile. Les processeurs sont devenus si rapides que l'utilisateur attend à peine qu'un processus exécute réellement le code à cause d'une action de l'utilisateur, donc tout cela est relatif. Cependant, si vous effectuez en fait un long processus, oui, vous le remarquerez. Le code suivant , par exemple fonctionne 40% plus lent sous DEBUG: AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length)).
Pieter van Ginkel

2
De plus, si vous êtes sur asp.netet utilisez le débogage au lieu de publier, certains scripts peuvent être ajoutés sur votre page, tels que: MicrosoftAjax.debug.jsqui a environ 7k lignes.
BrunoLM

13

Cela dépend fortement de la nature de votre application. Si votre application est lourde d'interface utilisateur, vous ne remarquerez probablement aucune différence puisque le composant le plus lent connecté à un ordinateur moderne est l'utilisateur. Si vous utilisez des animations d'interface utilisateur, vous voudrez peut-être tester si vous pouvez percevoir un décalage notable lors de l'exécution dans la version DEBUG.

Cependant, si vous avez de nombreux calculs lourds en calcul, vous remarquerez des différences (pouvant atteindre 40% comme @Pieter l'a mentionné, bien que cela dépende de la nature des calculs).

Il s'agit essentiellement d'un compromis de conception. Si vous publiez sous la version DEBUG, alors si les utilisateurs rencontrent des problèmes, vous pouvez obtenir un retraçage plus significatif et vous pouvez faire un diagnostic beaucoup plus flexible. En publiant dans la version DEBUG, vous évitez également que l'optimiseur produise des Heisenbugs obscurs .


11
  • D'après mon expérience, les applications de taille moyenne ou plus grandes sont sensiblement plus réactives dans une version Release. Faites un essai avec votre application et voyez comment elle se sent.

  • Une chose qui peut vous mordre avec les versions Release est que le code de construction Debug peut parfois supprimer les conditions de concurrence et d'autres bogues liés au thread. Un code optimisé peut entraîner une réorganisation des instructions et une exécution plus rapide peut exacerber certaines conditions de concurrence.


9

Vous ne devez jamais publier un build de débogage .NET en production. Il peut contenir du code laid pour prendre en charge Edit-and-Continue ou qui sait quoi d'autre. Pour autant que je sache, cela ne se produit que dans VB et non en C # (remarque: le message d'origine est étiqueté C #) , mais cela devrait encore donner raison de faire une pause quant à ce que Microsoft pense qu'ils sont autorisés à faire avec une version de débogage. En fait, avant .NET 4.0, le code VB perd de la mémoire proportionnellement au nombre d'instances d'objets avec des événements que vous construisez à l'appui de Edit-and-Continue. (Bien que ce problème soit signalé par https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , le code généré semble désagréable, créer des objets et les ajouter à une liste statique tout entenant un verrouWeakReference) Je ne veux certainement pas de ce type de support de débogage dans un environnement de production!


J'ai publié des versions de débogage plusieurs fois, et je n'ai jamais vu de problème. La seule différence peut-être, c'est que notre application côté serveur n'est pas une application Web prenant en charge un grand nombre d'utilisateurs. Mais c'est une application côté serveur avec une charge de traitement très élevée. D'après mon expérience, la différence entre Debug et Release semble complètement théorique. Je n'ai jamais vu de différence pratique avec l'une de nos applications.
Sam Goldberg

5

D'après mon expérience, la pire chose qui soit sortie du mode Release, ce sont les obscurs "bugs de publication". Étant donné que l'IL (langage intermédiaire) est optimisé en mode Release, il existe la possibilité de bogues qui ne se seraient pas manifestés en mode Debug. Il existe d'autres questions SO couvrant ce problème: Les raisons courantes des bogues dans la version de sortie ne sont pas présentes en mode débogage

Cela m'est arrivé une ou deux fois où une application console simple fonctionnerait parfaitement bien en mode débogage, mais étant donné la même entrée exacte, une erreur se produirait en mode Release. Ces bogues sont EXTRÊMEMENT difficiles à déboguer (par définition du mode Release, ironiquement).


Pour assurer le suivi, voici un article qui donne un exemple de bogue de version: codeproject.com/KB/trace/ReleaseBug.aspx
Roly

C'est toujours un problème si l'application est testée et approuvée avec les paramètres de débogage, même si elle supprime les erreurs, si cela entraîne l'échec de la génération de la version pendant le déploiement.
Øyvind Bråthen

4

Je dirais que 1) dépend en grande partie de votre mise en œuvre. Habituellement, la différence n'est pas si énorme. J'ai fait beaucoup de mesures et souvent je ne voyais pas de différence. Si vous utilisez du code non managé, beaucoup de tableaux énormes et des trucs comme ça, la différence de performances est légèrement plus grande, mais pas un monde différent (comme en C ++). 2) Habituellement, dans le code de version, moins d'erreurs sont affichées (tolérance plus élevée), donc un commutateur devrait fonctionner correctement.


1
Pour le code lié aux entrées-sorties, une version de version ne pourrait pas être plus rapide que le débogage.
Richard

0
    **Debug Mode:**
    Developer use debug mode for debugging the web application on live/local server. Debug mode allow developers to break the execution of program using interrupt 3 and step through the code. Debug mode has below features:
   1) Less optimized code
   2) Some additional instructions are added to enable the developer to set a breakpoint on every source code line.
   3) More memory is used by the source code at runtime.
   4) Scripts & images downloaded by webresource.axd are not cached.
   5) It has big size, and runs slower.

    **Release Mode:**
    Developer use release mode for final deployment of source code on live server. Release mode dlls contain optimized code and it is for customers. Release mode has below features:
   1) More optimized code
   2) Some additional instructions are removed and developer cant set a breakpoint on every source code line.
   3) Less memory is used by the source code at runtime.
   4) Scripts & images downloaded by webresource.axd are cached.
   5) It has small size, and runs fast.

2
il semble qu'en mode release parfois les premiers éléments d'une liste ne sont pas numérotés correctement. Certains éléments de la liste sont également dupliqués. :)
Gian Paolo
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.