Leistungsunterschiede zwischen Debug- und Release-Builds

280

Ich muss zugeben, dass ich mich normalerweise nicht darum gekümmert habe, zwischen den Debug- und Release- Konfigurationen in meinem Programm zu wechseln , und ich habe mich normalerweise für die Debug- Konfiguration entschieden, selbst wenn die Programme tatsächlich beim Kunden bereitgestellt werden.

Soweit ich weiß, besteht der einzige Unterschied zwischen diesen Konfigurationen, wenn Sie sie nicht manuell ändern, darin, dass bei Debug die DEBUGKonstante definiert und bei Release der Optimierungscode aktiviert ist.

Meine Fragen sind also zweifach:

  1. Gibt es große Leistungsunterschiede zwischen diesen beiden Konfigurationen? Gibt es einen bestimmten Codetyp, der hier große Leistungsunterschiede verursacht, oder ist er tatsächlich nicht so wichtig?

  2. Gibt es irgendeine Art von Code, der unter der Debug- Konfiguration einwandfrei ausgeführt wird und unter der Release- Konfiguration möglicherweise fehlschlägt , oder können Sie sicher sein, dass Code, der unter der Debug- Konfiguration getestet wurde und einwandfrei funktioniert, auch unter der Release-Konfiguration einwandfrei funktioniert.

Øyvind Bråthen
quelle
1
Siehe auch
BlueRaja - Danny Pflughoeft

Antworten:

511

Der C # -Compiler selbst ändert die emittierte IL im Release-Build nicht wesentlich. Bemerkenswert ist, dass keine NOP-Opcodes mehr ausgegeben werden, mit denen Sie einen Haltepunkt für eine geschweifte Klammer festlegen können. Der große ist der Optimierer, der in den JIT-Compiler integriert ist. Ich weiß, dass es die folgenden Optimierungen vornimmt:

  • Methode Inlining. Ein Methodenaufruf wird durch das Einfügen des Codes der Methode ersetzt. Dies ist eine große Sache, es macht Immobilien-Accessoren im Wesentlichen kostenlos.

  • CPU-Registerzuordnung. Lokale Variablen und Methodenargumente können in einem CPU-Register gespeichert bleiben, ohne jemals (oder weniger häufig) im Stapelrahmen gespeichert zu werden. Dies ist eine große Sache, die das Debuggen von optimiertem Code so schwierig macht. Und dem flüchtigen Schlüsselwort eine Bedeutung geben.

  • Eliminierung der Array-Indexprüfung. Eine wichtige Optimierung bei der Arbeit mit Arrays (alle .NET-Auflistungsklassen verwenden intern ein Array). Wenn der JIT-Compiler überprüfen kann, dass eine Schleife ein Array niemals außerhalb der Grenzen indiziert, wird die Indexprüfung aufgehoben. Großes.

  • Schleife abrollen. Schleifen mit kleinen Körpern werden verbessert, indem der Code bis zu viermal im Körper wiederholt wird und weniger Schleifen ausgeführt werden. Reduziert die Verzweigungskosten und verbessert die superskalaren Ausführungsoptionen des Prozessors.

  • Eliminierung des toten Codes. Eine Aussage wie if (false) {/ ... /} wird vollständig eliminiert. Dies kann durch ständiges Falten und Inlining auftreten. In anderen Fällen kann der JIT-Compiler feststellen, dass der Code keine möglichen Nebenwirkungen hat. Diese Optimierung macht das Profilieren von Code so schwierig.

  • Code heben. Code innerhalb einer Schleife, der nicht von der Schleife betroffen ist, kann aus der Schleife verschoben werden. Der Optimierer eines C-Compilers wird viel mehr Zeit damit verbringen, Möglichkeiten zum Heben zu finden. Aufgrund der erforderlichen Datenflussanalyse ist dies jedoch eine teure Optimierung, und der Jitter kann sich die Zeit nicht leisten, sodass nur offensichtliche Fälle angehoben werden. Erzwingen, dass .NET-Programmierer besseren Quellcode schreiben und sich selbst hochziehen.

  • Gemeinsame Eliminierung von Subausdrücken. x = y + 4; z = y + 4; wird z = x; Ziemlich häufig in Anweisungen wie dest [ix + 1] = src [ix + 1]; Zur besseren Lesbarkeit geschrieben, ohne eine Hilfsvariable einzuführen. Die Lesbarkeit muss nicht beeinträchtigt werden.

  • Ständiges Falten. x = 1 + 2; wird x = 3; Dieses einfache Beispiel wird vom Compiler frühzeitig erkannt, tritt jedoch zur JIT-Zeit auf, wenn andere Optimierungen dies ermöglichen.

  • Weitergabe kopieren. x = a; y = x; wird y = a; Dies hilft dem Registerzuweiser, bessere Entscheidungen zu treffen. Es ist eine große Sache im x86-Jitter, weil es nur wenige Register gibt, mit denen man arbeiten kann. Die Auswahl der richtigen ist für die Perfektion von entscheidender Bedeutung.

Dies sind sehr wichtige Optimierungen, die einen großen Unterschied machen können, wenn Sie beispielsweise den Debug-Build Ihrer App profilieren und mit dem Release-Build vergleichen. Das ist jedoch nur dann wirklich wichtig, wenn sich der Code auf Ihrem kritischen Pfad befindet. Die 5 bis 10% des Codes, den Sie schreiben, wirken sich tatsächlich auf die Leistung Ihres Programms aus. Der JIT-Optimierer ist nicht intelligent genug, um im Voraus zu wissen, was wichtig ist. Er kann nur das Einstellrad "Drehen auf elf" für den gesamten Code anwenden.

Das effektive Ergebnis dieser Optimierungen für die Ausführungszeit Ihres Programms wird häufig durch Code beeinflusst, der an anderer Stelle ausgeführt wird. Lesen einer Datei, Ausführen einer Datenbankabfrage usw. Die Arbeit des JIT-Optimierers wird vollständig unsichtbar. Es macht aber nichts aus :)

Der JIT-Optimierer ist ein ziemlich zuverlässiger Code, vor allem, weil er millionenfach getestet wurde. Es ist äußerst selten, dass Probleme in der Release-Build-Version Ihres Programms auftreten. Es passiert jedoch. Sowohl der x64- als auch der x86-Jitter hatten Probleme mit Strukturen. Der x86-Jitter hat Probleme mit der Gleitkommakonsistenz und führt zu geringfügig unterschiedlichen Ergebnissen, wenn die Zwischenprodukte einer Gleitkommaberechnung mit einer Genauigkeit von 80 Bit in einem FPU-Register gespeichert werden, anstatt beim Löschen in den Speicher abgeschnitten zu werden.

Hans Passant
quelle
23
Ich denke nicht, dass alle Sammlungen Arrays verwenden: LinkedList<T>nicht, obwohl es nicht sehr oft verwendet wird.
Svick
Ich denke, die CLR konfiguriert die FPU mit einer Genauigkeit von 53 Bit (passend zu 64 Bit breiten Doppelwerten), daher sollte es keine erweiterten 80-Bit-Doppelberechnungen für Float64-Werte geben. Float32-Berechnungen können jedoch mit dieser 53-Bit-Genauigkeit berechnet und nur abgeschnitten werden, wenn sie im Speicher gespeichert sind.
Govert
2
Das volatileSchlüsselwort gilt nicht für lokale Variablen, die in einem Stapelrahmen gespeichert sind. Aus der Dokumentation unter msdn.microsoft.com/en-us/library/x13ttww7.aspx : "Das Schlüsselwort volatile kann nur auf Felder einer Klasse oder Struktur angewendet werden. Lokale Variablen können nicht als flüchtig deklariert werden."
Kris Vandermotten
8
Als bescheidene Änderung denke ich, was den Unterschied zwischen Debugund Releasein dieser Hinsicht wirklich ausmacht, ist das Kontrollkästchen "Code optimieren", das normalerweise aktiviert, Releaseaber deaktiviert ist Debug. Es soll nur sichergestellt werden, dass die Leser nicht glauben, dass es "magische", unsichtbare Unterschiede zwischen den beiden Build-Konfigurationen gibt, die über das hinausgehen, was auf der Projekteigenschaftsseite in Visual Studio zu finden ist.
Chiccodoro
3
Erwähnenswert ist vielleicht, dass praktisch keine der Methoden in System.Diagnostics.Debug irgendetwas in einem Debug-Build tut. Auch Variablen werden nicht so schnell finalisiert, siehe ( stackoverflow.com/a/7165380/20553 ).
Martin Brown
23
  1. Ja, es gibt viele Leistungsunterschiede, die wirklich für Ihren gesamten Code gelten. Das Debuggen führt nur sehr wenig Leistungsoptimierung und den Release-Modus sehr wenig durch.

  2. Nur Code, der auf der DEBUGKonstante basiert, kann bei einem Release-Build eine andere Leistung erbringen. Außerdem sollten Sie keine Probleme sehen.

Ein Beispiel für Framework-Code, der von der DEBUGKonstante abhängt, ist die Debug.Assert()Methode, für die das Attribut [Conditional("DEBUG)"]definiert ist. Dies bedeutet, dass dies auch von der DEBUGKonstante abhängt und dies nicht im Release-Build enthalten ist.

Pieter van Ginkel
quelle
2
Das ist alles wahr, aber könnten Sie jemals einen Unterschied messen? Oder bemerken Sie einen Unterschied bei der Verwendung eines Programms? Natürlich möchte ich niemanden ermutigen, seine Software im Debug-Modus zu veröffentlichen, aber die Frage war, ob es einen großen Leistungsunterschied gibt und ich kann das nicht sehen.
Testalino
2
Erwähnenswert ist auch, dass Debug-Versionen in viel höherem Maße mit dem ursprünglichen Quellcode korrelieren als Release-Versionen. Wenn Sie der Meinung sind (wie unwahrscheinlich es auch sein mag), dass jemand versucht, Ihre ausführbaren Dateien zurückzuentwickeln, möchten Sie dies nicht durch die Bereitstellung von Debug-Versionen vereinfachen.
Jwheron
2
@ Testalino - Nun, heutzutage ist es schwierig. Prozessoren sind so schnell geworden, dass der Benutzer aufgrund einer Benutzeraktion kaum darauf wartet, dass ein Prozess tatsächlich Code ausführt. Dies ist also alles relativ. Wenn Sie jedoch tatsächlich einen längeren Prozess durchführen, werden Sie es bemerken. Der folgende Code läuft zB 40% langsamer unter DEBUG: AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length)).
Pieter van Ginkel
2
Wenn Sie asp.netaktiviert sind und Debug anstelle von Release verwenden, werden möglicherweise einige Skripte auf Ihrer Seite hinzugefügt, z. MicrosoftAjax.debug.jsB .: Das hat ungefähr 7.000 Zeilen.
BrunoLM
13

Dies hängt stark von der Art Ihrer Anwendung ab. Wenn Ihre Anwendung UI-lastig ist, werden Sie wahrscheinlich keinen Unterschied bemerken, da der Benutzer die langsamste Komponente ist, die mit einem modernen Computer verbunden ist. Wenn Sie einige Benutzeroberflächenanimationen verwenden, möchten Sie möglicherweise testen, ob Sie beim Ausführen in DEBUG Build eine merkliche Verzögerung feststellen können.

Wenn Sie jedoch viele rechenintensive Berechnungen haben, werden Sie Unterschiede feststellen (können bis zu 40% betragen, wie von @Pieter erwähnt, obwohl dies von der Art der Berechnungen abhängt).

Es ist im Grunde ein Design-Kompromiss. Wenn Sie unter DEBUG-Build freigeben und bei den Benutzern Probleme auftreten, können Sie einen aussagekräftigeren Traceback erhalten und eine viel flexiblere Diagnose durchführen. Durch die Veröffentlichung in DEBUG Build vermeiden Sie auch, dass der Optimierer obskure Heisenbugs erzeugt .

Lie Ryan
quelle
11
  • Ich habe die Erfahrung gemacht, dass mittelgroße oder größere Anwendungen in einem Release-Build deutlich schneller reagieren. Probieren Sie es mit Ihrer Anwendung aus und sehen Sie, wie es sich anfühlt.

  • Eine Sache, die Sie bei Release-Builds beißen kann, ist, dass Debug-Build-Code manchmal Race-Bedingungen und andere Threading-bezogene Fehler unterdrücken kann. Optimierter Code kann zu einer Neuordnung der Anweisungen führen, und eine schnellere Ausführung kann bestimmte Rennbedingungen verschärfen.

Dan Bryant
quelle
9

Sie sollten niemals ein .NET-Debug-Build für die Produktion freigeben. Es kann hässlichen Code enthalten, der das Bearbeiten und Fortfahren unterstützt, oder wer weiß was noch. Soweit ich weiß, geschieht dies nur in VB, nicht in C # (Hinweis: Der ursprüngliche Beitrag ist mit C # gekennzeichnet) , aber es sollte dennoch Anlass geben, darüber nachzudenken, was Microsoft mit einem Debug-Build tun darf. Tatsächlich verliert VB-Code vor .NET 4.0 Speicher, der proportional zur Anzahl der Instanzen von Objekten mit Ereignissen ist, die Sie zur Unterstützung von Edit-and-Continue erstellen. (Obwohl dies gemäß https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , dem generierten Code , behoben wurde sieht böse aus, erstellt WeakReferenceObjekte und fügt sie währenddessen einer statischen Liste hinzueine Sperre halten ) Ich möchte auf keinen Fall diese Art von Debugging-Unterstützung in einer Produktionsumgebung!

Jason Kresowaty
quelle
Ich habe Debug-Builds viele Male veröffentlicht und nie ein Problem gesehen. Der einzige Unterschied besteht möglicherweise darin, dass unsere serverseitige Anwendung keine Web-App ist, die viele Benutzer unterstützt. Es handelt sich jedoch um eine serverseitige Anwendung mit sehr hoher Verarbeitungslast. Aus meiner Erfahrung scheint der Unterschied zwischen Debug und Release völlig theoretisch. Ich habe noch nie einen praktischen Unterschied mit einer unserer Apps gesehen.
Sam Goldberg
5

Nach meiner Erfahrung sind die obskuren "Release Bugs" das Schlimmste, was aus dem Release-Modus herausgekommen ist. Da die IL (Zwischensprache) im Release-Modus optimiert ist, besteht die Möglichkeit von Fehlern, die sich im Debug-Modus nicht manifestiert hätten. Es gibt andere SO-Fragen zu diesem Problem: Häufige Gründe für Fehler in der Release-Version, die im Debug-Modus nicht vorhanden sind

Dies ist mir ein- oder zweimal passiert, wenn eine einfache Konsolen-App im Debug-Modus einwandfrei funktioniert, bei genau derselben Eingabe jedoch im Release-Modus ein Fehler auftritt. Diese Fehler sind EXTREM schwer zu debuggen (ironischerweise per Definition des Release-Modus).

Roly
quelle
Um zu folgen, hier ist ein Artikel, der ein Beispiel für einen Release- Fehler
Roly
Es ist immer noch ein Problem, wenn die Anwendung mit den Debug-Einstellungen getestet und genehmigt wird, auch wenn sie Fehler unterdrückt, wenn dies dazu führt, dass der Release-Build während der Bereitstellung fehlschlägt.
Øyvind Bråthen
4

Ich würde sagen, dass 1) weitgehend von Ihrer Implementierung abhängt. Normalerweise ist der Unterschied nicht so groß. Ich habe viele Messungen durchgeführt und oft konnte ich keinen Unterschied feststellen. Wenn Sie nicht verwalteten Code, viele große Arrays und ähnliches verwenden, ist der Leistungsunterschied etwas größer, aber keine andere Welt (wie in C ++). 2) Normalerweise werden im Release-Code weniger Fehler angezeigt (höhere Toleranz), daher sollte ein Schalter einwandfrei funktionieren.

Testalino
quelle
1
Für Code, der an E / A gebunden ist, kann ein Release-Build nicht schneller sein als das Debuggen.
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.
Nandha Kumar
quelle
2
Es scheint, als ob im Release-Modus manchmal die ersten Elemente einer Liste nicht richtig nummeriert sind. Außerdem werden einige Elemente in der Liste dupliziert. :)
Gian Paolo