Ist es wirklich eine gute Praxis, Optimierungen während der Entwicklungs- und Debugging-Phasen zu deaktivieren?

Ich habe Programming 16-Bit PIC Microcontrollers in C gelesen , und es gibt diese Bestätigung im Buch:

Während der Entwicklungs- und Debugging-Phasen eines Projekts empfiehlt es sich jedoch immer, alle Optimierungen zu deaktivieren, da sie die Struktur des analysierten Codes ändern und Einzelschritte und die Platzierung von Haltepunkten problematisch machen könnten.

Ich gestehe, ich war etwas verwirrt. Ich habe nicht verstanden, ob der Autor das wegen des C30 -Evaluierungszeitraums gesagt hat oder ob es wirklich eine gute Praxis ist.

Ich würde gerne wissen, ob Sie diese Praxis tatsächlich anwenden und warum?

Antworten (12)

Dies ist im gesamten Software-Engineering ziemlich üblich - wenn Sie Code optimieren, darf der Compiler die Dinge so ziemlich neu anordnen, wie er will, solange Sie keinen Unterschied im Betrieb feststellen können. Wenn Sie beispielsweise eine Variable in jeder Iteration einer Schleife initialisieren und die Variable innerhalb der Schleife nie ändern, darf der Optimierer diese Initialisierung aus der Schleife verschieben, sodass Sie keine Zeit damit verschwenden.

Es könnte auch erkennen, dass Sie eine Zahl berechnen, mit der Sie dann nichts tun, bevor Sie sie überschreiben. In diesem Fall könnte es die nutzlose Berechnung eliminieren.

Das Problem bei der Optimierung besteht darin, dass Sie einen Haltepunkt auf einen Codeabschnitt setzen möchten, den der Optimierer verschoben oder eliminiert hat. In diesem Fall kann der Debugger nicht das tun, was Sie wollen (im Allgemeinen setzt er den Haltepunkt irgendwo in die Nähe). Damit der generierte Code dem, was Sie geschrieben haben, ähnlicher wird, deaktivieren Sie die Optimierungen während des Debuggens. Dadurch wird sichergestellt, dass der Code, den Sie unterbrechen möchten, wirklich vorhanden ist.

Sie müssen damit jedoch vorsichtig sein, da die Optimierung je nach Code Dinge beschädigen kann! Im Allgemeinen ist Code, der von einem korrekt funktionierenden Optimierer beschädigt wird, wirklich nur fehlerhafter Code, der mit etwas davonkommt, also möchten Sie normalerweise herausfinden, warum der Optimierer ihn beschädigt.

Der Kontrapunkt zu diesem Argument ist, dass der Optimierer die Dinge wahrscheinlich kleiner und/oder schneller machen wird, und wenn Sie Code mit Zeit- oder Größenbeschränkung haben, können Sie etwas kaputt machen, indem Sie die Optimierung deaktivieren, und Ihre Zeit damit verschwenden, ein Problem zu debuggen, das dies nicht tut. t wirklich existieren. Natürlich kann der Debugger Ihren Code auch langsamer und größer machen.
Wo kann ich mehr darüber erfahren?
Ich habe nicht mit dem C30-Compiler gearbeitet, aber für den C18-Compiler gab es einen App-Hinweis/ein Handbuch für den Compiler, in dem beschrieben wurde, welche Optimierungen er unterstützt.
@O Engenheiro: Überprüfen Sie Ihre Compiler-Dokumentation auf unterstützte Optimierungen. Die Optimierung variiert stark je nach Compiler, Bibliotheken und Zielarchitektur.
Wiederum nicht für den C30-Compiler, aber gcc veröffentlicht eine LANGE Liste der verschiedenen Optimierungen, die angewendet werden können. Sie können diese Liste auch verwenden, um eine feinkörnige Optimierung zu erhalten, falls Sie eine bestimmte Kontrollstruktur haben, die Sie intakt halten möchten. Die Liste ist hier: gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
@Reemrevnivek, ich würde einzelne Dateioptimierungen deaktivieren, was sich auf das Gesamttiming auswirkt, aber dann die Datei zum Benchmarken und Testen anderer Systeme verwenden. Bei allen Optimierungen würde ich LED-Blinken verwenden und solche mit einem Oszilloskop messen, um Timings zu messen.
@OEngenheiro, Werfen Sie einen Blick auf einige der Bücher, die Jack Ganslle empfiehlt, wenn Sie auf eingebettete Systeme angewendet werden möchten. Andernfalls würde ich davon ausgehen, dass es eine gute Möglichkeit wäre, SO zu fragen, um die CS-Bücher zu dieser Praxis zu bekommen.

Ich habe diese Frage an Jack Ganssle geschickt und er hat mir folgendes geantwortet:

Daniel,

Ich ziehe es vor, mit den Optimierungen zu debuggen, die im veröffentlichten Code enthalten sein werden. Die NASA sagt: "Teste, was du fliegst, flieg, was du testest." Mit anderen Worten, führen Sie keine Tests durch und ändern Sie dann den Code!

Manchmal muss man jedoch Optimierungen ausschalten, damit der Debugger funktioniert. Ich versuche, sie nur in den Modulen auszuschalten, an denen ich arbeite. Aus diesem Grund glaube ich daran, Dateien klein zu halten, sagen wir ein paar hundert Zeilen Code oder so.

Alles Gute, Jack

Ich denke, es gibt einen unausgesprochenen Unterschied zwischen den beiden Absätzen in dieser Antwort. Testen bezieht sich auf ein Verfahren, das zeigen soll, dass die weiche, feste und/oder harte Ware ordnungsgemäß funktioniert. Debugging ist der Prozess, bei dem der Code Anweisung für Anweisung durchlaufen wird, um zu sehen, warum er [noch] nicht funktioniert.
Es ist schön, die Wahl zu haben. So können die Tests mehr Sorten/Permutationen mit und ohne Optimierung abdecken. Je mehr Abdeckung, desto besser ist das Testen
@reemrevnivek, wenn du debuggst, testest du nicht auch?
@O Engenheiro - Nein. Ich debugge nur, wenn der Test fehlschlägt.

Hängt davon ab, und das gilt im Allgemeinen für alle Tools, nicht nur für C30.

Optimierungen entfernen und/oder restrukturieren den Code häufig auf verschiedene Weise. Ihre switch-Anweisung kann mit einem if/else-Konstrukt neu implementiert oder in einigen Fällen vollständig entfernt werden. y = x * 16 kann durch eine Reihe von Linksverschiebungen usw. ersetzt werden. Obwohl diese letzte Art der Optimierung normalerweise immer noch schrittweise durchlaufen werden kann, ist es hauptsächlich die Umstrukturierung der Steueranweisung, die Sie erreicht.

Dies kann es unmöglich machen, einen Debugger schrittweise durch Ihren C-Code zu führen, weil die Strukturen, die Sie in C definiert haben, nicht mehr existieren, sie vom Compiler ersetzt oder neu geordnet wurden, sodass der Compiler glaubt, dass sie schneller sind oder weniger Platz benötigen. Es kann auch dazu führen, dass Breakpoints nicht aus dem C-Listing gesetzt werden können, da die Anweisung, bei der Sie aufbrechen, möglicherweise nicht mehr existiert. Beispielsweise können Sie versuchen, einen Haltepunkt in einer if-Anweisung zu setzen, aber der Compiler hat dieses if möglicherweise entfernt. Sie können versuchen, einen Haltepunkt innerhalb einer While- oder For-Schleife zu setzen, aber der Compiler hat entschieden, diese Schleife aufzuheben, sodass sie nicht mehr existiert.

Aus diesem Grund ist es in der Regel einfacher, wenn Sie mit deaktivierten Optimierungen debuggen können. Sie sollten immer mit aktivierten Optimierungen erneut testen. Dies ist ungefähr der einzige Weg, wie Sie herausfinden, dass Sie einen wichtigen Punkt verpasst haben volatileund dass dies zu zeitweiligen Ausfällen (oder einer anderen Verrücktheit) führt.

Bei der Embedded-Entwicklung muss man ohnehin mit Optimierungen aufpassen. Insbesondere in zeitkritischen Codeabschnitten, z. B. einigen Interrupts. In diesen Fällen sollten Sie entweder die kritischen Bits in Assembly codieren oder Compiler-Direktiven verwenden, um sicherzustellen, dass diese Abschnitte nicht optimiert sind, damit Sie wissen, dass sie eine feste Ausführungszeit oder eine feste Worst-Case-Laufzeit haben.

Das andere Problem kann das Einpassen von Code in den uC sein. Möglicherweise benötigen Sie Optimierungen der Codedichte, um Ihren Code einfach in den Chip einzupassen. Dies ist einer der Gründe, warum es normalerweise eine gute Idee ist, mit dem größten ROM-Kapazitäts-uC in einer Familie zu beginnen und nur einen kleineren für die Herstellung auszuwählen, nachdem Ihr Code gesperrt ist.

Im Allgemeinen würde ich mit den Einstellungen debuggen, mit denen ich die Veröffentlichung geplant hatte. Wenn ich optimierten Code veröffentlichen würde, würde ich mit optimiertem Code debuggen. Wenn ich nicht optimierten Code veröffentlichen würde, würde ich mit nicht optimiertem Code debuggen. Ich mache das aus zwei Gründen. Erstens können Optimierer ausreichend große Timing-Unterschiede vornehmen, um zu bewirken, dass sich das Endprodukt anders verhält als nicht optimierter Code. Zweitens machen Compiler-Anbieter Fehler, obwohl die meisten ziemlich gut sind, und optimierter Code kann andere Ergebnisse als nicht optimierter Code erzeugen. Aus diesem Grund möchte ich so viel Testzeit wie möglich erhalten, unabhängig davon, mit welcher Einstellung ich veröffentlichen möchte.

Allerdings können Optimierer das Debuggen erschweren, wie in den vorherigen Antworten erwähnt. Wenn ich einen bestimmten Codeabschnitt finde, der schwer zu debuggen ist, schalte ich den Optimierer vorübergehend aus, führe das Debugging durch, damit der Code funktioniert, schalte dann den Optimierer wieder ein und teste erneut.

Bei aktivierten Optimierungen kann es jedoch fast unmöglich sein, den Code einzeln zu durchlaufen. Debuggen Sie mit deaktivierten Optimierungen und führen Sie Ihre Komponententests mit Release-Code aus.

Meine normale Strategie besteht darin, mit der endgültigen Optimierung zu entwickeln (Max für Größe oder Geschwindigkeit, je nach Bedarf), aber die Optimierung vorübergehend auszuschalten, wenn ich etwas debuggen oder verfolgen muss. Dies verringert das Risiko, dass Fehler als Folge von sich ändernden Optimierungsstufen auftauchen.

Ein typischer Fehlermodus ist, wenn eine zunehmende Optimierung zuvor ungesehene Fehler zum Auftauchen bringt, weil Sie Variablen nicht wo nötig als flüchtig deklariert haben - dies ist wichtig, um dem Compiler mitzuteilen, welche Dinge nicht "optimiert" werden sollten.

bei Breakpoints ist es sicherlich sinnvoll... da der Compiler viele Anweisungen entfernen kann, die sich nicht wirklich auf den Speicher auswirken.

überlegen Sie sich etwas wie:

int i =0;

for (int j=0; j < 10; j++)
{
 i+=j;
}
return 0;

komplett ausoptimiert werden könnte (weil inie ausgelesen wird). Aus der Sicht Ihres Haltepunkts würde es so aussehen, als ob der gesamte Code übersprungen wurde, obwohl er im Grunde gar nicht da war .... Ich nehme an, deshalb sehen Sie in Sleep-Typ-Funktionen oft so etwas wie:

for (int j=delay; j != 0; j--)
{
    asm( " nop " );
    asm( " nop " );
}
return 0;

Verwenden Sie das Formular, mit dem Sie veröffentlichen werden, Debugger und das Kompilieren zum Debuggen verbergen viele (VIELE) Fehler, die Sie nicht sehen, bis Sie für die Veröffentlichung kompilieren. Bis dahin ist es viel schwieriger, diese Fehler zu finden, als das Debuggen, während Sie gehen. 20 Jahre jetzt und ich hatte nie eine Verwendung für einen GDB oder einen ähnlichen Debugger, keine Notwendigkeit, Variablen oder Einzelschritte zu beobachten. Hunderte bis Tausende Zeilen pro Tag. Es ist also möglich, lassen Sie sich nicht anders denken.

Das Kompilieren für das Debuggen und das spätere Kompilieren für die Veröffentlichung kann und wird doppelt bis mehr als doppelt so viel Aufwand erfordern. Wenn Sie in Schwierigkeiten geraten und ein Tool wie einen Debugger verwenden müssen, kompilieren Sie, damit der Debugger das spezifische Problem durcharbeitet, und kehren Sie dann zum normalen Betrieb zurück.

Andere Probleme sind auch wahr, wie der Optimierer den Code schneller macht, also für eingebettete insbesondere Ihre Timing-Änderungen mit Compiler-Optionen und die die Funktionalität Ihres Programms beeinträchtigen können, verwenden Sie auch hier die lieferbare Kompilierungsauswahl während der gesamten Phase. Compiler sind auch Programme und haben Bugs und Optimierer machen Fehler und einige glauben nicht daran. Wenn das der Fall ist, spricht nichts dagegen, ohne Optimierung zu kompilieren, machen Sie es einfach immer so. Der Weg, den ich bevorzuge, ist, zur Optimierung zu kompilieren, und wenn ich ein Compiler-Problem vermute, deaktiviere die Optimierung, wenn das Problem dadurch behoben wird, gehe normalerweise hin und her und untersuche manchmal die Assembler-Ausgabe, um herauszufinden, warum.

+1, nur um Ihre gute Antwort näher zu erläutern: Durch das Kompilieren im "Debug" -Modus wird der Stapel / Haufen häufig um Variablen mit nicht zugewiesenem Speicherplatz herum aufgefüllt, um kleinere Write-off-the-End- und Format-String-Fehler zu verringern. Sie erhalten häufiger einen guten Laufzeitabsturz, wenn Sie in der Version kompilieren.

Ich entwickle Code immer mit -O0 (gcc-Option zum Deaktivieren der Optimierung). Wenn ich das Gefühl habe, dass ich an dem Punkt angelangt bin, an dem ich anfangen möchte, die Dinge mehr in Richtung einer Veröffentlichung zu bewegen, beginne ich mit -Os (für Größe optimieren), da es im Allgemeinen umso besser wird, je mehr Code Sie im Cache behalten können. auch wenn es nicht super-duper optimiert ist.

Ich finde, dass gdb viel besser mit -O0-Code funktioniert, und es ist viel einfacher zu folgen, wenn Sie in die Assembly einsteigen müssen. Durch Umschalten zwischen -O0 und -Os können Sie auch sehen, was der Compiler mit Ihrem Code macht. Es ist manchmal eine ziemlich interessante Ausbildung und kann auch Compiler-Bugs aufdecken ... diese fiesen Dinge, die Sie dazu bringen, sich die Haare zu raufen, wenn Sie versuchen, herauszufinden, was mit Ihrem Code nicht stimmt!

Wenn es wirklich nötig ist, beginne ich mit dem Hinzufügen von -fdata-sections und -fcode-sections mit --gc-sections, die es dem Linker ermöglichen, ganze Funktionen und Datensegmente zu entfernen, die nicht wirklich verwendet werden. Es gibt viele kleine Dinge, an denen Sie herumbasteln können, um zu versuchen, die Dinge weiter zu verkleinern oder schneller zu machen, aber im Großen und Ganzen sind dies die einzigen Tricks, die ich verwende, und alles, was kleiner oder schneller sein muss, werde ich geben -montieren.

Ja, das Deaktivieren von Optimierungen während des Debuggens ist aus drei Gründen seit einiger Zeit eine bewährte Methode:

  • (a) Wenn Sie das Programm mit einem High-Level-Debugger in Einzelschritten ausführen, ist es etwas weniger verwirrend.
  • (a) (veraltet) Wenn Sie das Programm in einem einzigen Schritt mit einem Assembler-Debugger debuggen, ist es viel weniger verwirrend. (Aber warum sollten Sie sich damit beschäftigen, wenn Sie einen High-Level-Debugger verwenden könnten?)
  • (b) (längst veraltet) Sie werden diese bestimmte ausführbare Datei wahrscheinlich nur einmal ausführen, dann einige Änderungen vornehmen und neu kompilieren. Es ist Zeitverschwendung, weitere 10 Minuten zu warten, während der Compiler diese bestimmte ausführbare Datei "optimiert", wenn dadurch weniger als 10 Minuten Laufzeit eingespart werden. (Dies ist bei modernen PCs nicht mehr relevant, die eine typische ausführbare Mikrocontroller-Datei mit vollständiger Optimierung in weniger als 2 Sekunden kompilieren können).

Viele Menschen gehen sogar noch weiter in diese Richtung und versenden mit eingeschalteten Behauptungen .

Single-Step durch Assemblercode kann sehr nützlich sein, um Fälle zu diagnostizieren, in denen der Quellcode tatsächlich etwas anderes angibt, als es aussieht (z. B. "longvar1 &= ~0x40000000; longvar2 &= ~0x80000000;") oder in denen ein Compiler fehlerhaften Code generiert . Ich habe einige Probleme mit Maschinencode-Debuggern aufgespürt, von denen ich wirklich nicht glaube, dass ich sie auf andere Weise hätten aufspüren können.

Ganz einfach: Optimierungen sind zeitintensiv und können nutzlos sein, wenn Sie diesen Codeabschnitt später in der Entwicklung ändern müssen. Sie können also Zeit- und Geldverschwendung sein.
Sie sind jedoch für fertige Module nützlich; Teile des Codes, die höchstwahrscheinlich keine Änderungen mehr benötigen.

Wenn Sie den Debugger verwenden, würde ich Optimierungen deaktivieren und Debug aktivieren.

Persönlich finde ich, dass der PIC-Debugger mehr Probleme verursacht, als er mir hilft, ihn zu beheben.
Ich verwende einfach printf() für USART, um meine in C18 geschriebenen Programme zu debuggen.

Die meisten Argumente gegen die Aktivierung der Optimierung beim Kompilieren sind:

  1. Probleme mit dem Debuggen (JTAG-Konnektivität, Haltepunkte usw.)
  2. falsches Software-Timing
  3. sh*t funktioniert nicht mehr richtig

IMHO sind die ersten beiden echt, die dritte nicht so sehr. Es bedeutet oft, dass Sie einen schlechten Code haben oder sich auf eine unsichere Ausnutzung der Sprache/Implementierung verlassen oder vielleicht ist der Autor nur ein Fan des guten alten Uncle Undefined Behaviour.

Der Embedded in Academia-Blog hat ein oder zwei Dinge über undefiniertes Verhalten zu sagen, und dieser Beitrag behandelt, wie Compiler es ausnutzen: http://blog.regehr.org/archives/761

Ein weiterer möglicher Grund ist, dass der Compiler störend langsam sein kann, wenn Optimierungen aktiviert sind.