it-swarm.com.de

Kann die Compiler-Optimierung Fehler einführen?

Heute hatte ich eine Diskussion mit einem Freund von mir und wir diskutierten ein paar Stunden über "Compiler-Optimierung".

Ich habe den Punkt verteidigt, dass manchmal eine Compiler-Optimierung Fehler oder zumindest unerwünschtes Verhalten einführt.

Mein Freund stimmte dem absolut nicht zu und sagte, dass "Compiler von intelligenten Leuten gebaut werden und intelligente Dinge tun" und somit niemals / etwas falsch machen können.

Er hat mich überhaupt nicht überzeugt, aber ich muss zugeben, dass es mir an wirklichen Beispielen fehlt, um meinen Standpunkt zu stärken.

Wer ist hier richtig? Wenn ja, haben Sie ein reales Beispiel, bei dem eine Compiler-Optimierung einen Fehler in der resultierenden Software verursacht hat? Wenn ich mich irre, sollte ich aufhören zu programmieren und stattdessen Angeln lernen?

68
ereOn

Compiler-Optimierungen können Fehler oder unerwünschtes Verhalten verursachen. Deshalb können Sie sie ausschalten.

Ein Beispiel: Ein Compiler kann den Lese-/Schreibzugriff auf einen Speicherort optimieren, beispielsweise doppelte Lese- oder Schreibvorgänge beseitigen oder bestimmte Vorgänge neu anordnen. Wenn der betreffende Speicherplatz nur von einem einzelnen Thread verwendet wird und tatsächlich Speicherplatz ist, kann dies in Ordnung sein. Wenn der Speicherort jedoch ein Hardware-Register IO ist, kann das Neuanordnen oder Entfernen von Schreibvorgängen völlig falsch sein. In dieser Situation müssen Sie normalerweise Code schreiben, wissend, dass der Compiler ihn möglicherweise "optimiert" und somit weiß, dass der naive Ansatz nicht funktioniert.

Update: Wie Adam Robinson in einem Kommentar ausgeführt hat, handelt es sich bei dem oben beschriebenen Szenario eher um einen Programmierfehler als um einen Optimiererfehler. Der Punkt, den ich zu veranschaulichen versuchte, ist, dass einige Programme, die ansonsten korrekt sind, in Kombination mit Optimierungen, die ansonsten einwandfrei funktionieren, Fehler in das Programm einführen können, wenn sie miteinander kombiniert werden. In einigen Fällen lautet die Sprachspezifikation: "Sie müssen die Dinge auf diese Weise ausführen, da diese Art von Optimierung auftreten kann und Ihr Programm fehlschlägt". In diesem Fall handelt es sich um einen Fehler im Code. Manchmal verfügt ein Compiler jedoch über eine (normalerweise optionale) Optimierungsfunktion, die falschen Code generieren kann, weil der Compiler zu sehr versucht, den Code zu optimieren, oder nicht erkennen kann, dass die Optimierung ungeeignet ist. In diesem Fall muss der Programmierer wissen, wann die betreffende Optimierung sicher aktiviert werden kann.

Ein anderes Beispiel: Der Kernel linux hatte einen Fehler , bei dem ein potenziell NULL - Zeiger dereferenziert wurde, bevor ein Test für diesen Zeiger den Wert null hat. In einigen Fällen war es jedoch möglich, den Speicher der Adresse Null zuzuordnen, sodass die Dereferenzierung erfolgreich durchgeführt werden konnte. Als der Compiler bemerkte, dass der Zeiger dereferenziert war, ging er davon aus, dass er nicht NULL sein konnte, und entfernte den NULL-Test später und den gesamten Code in diesem Zweig. Dies führte zu einer Sicherheitsanfälligkeit im Code, da die Funktion einen ungültigen Zeiger verwenden würde, der vom Angreifer bereitgestellte Daten enthält. In Fällen, in denen der Zeiger legitim Null war und der Speicher nicht der Adresse Null zugeordnet wurde, würde der Kernel wie zuvor OOPS bleiben. Vor der Optimierung enthielt der Code also einen Fehler. nachdem es zwei enthielt, und einer von ihnen erlaubte ein lokales Root-Exploit. 

CERT hat eine Präsentation namens "Gefährliche Optimierungen und der Verlust der Kausalität" von Robert C. Seacord, die viele Optimierungen aufführt, die Fehler in Programme einführen (oder aufdecken). Es werden die verschiedenen Arten von Optimierungen beschrieben, die möglich sind, von "tun, was die Hardware tut", "alles mögliche undefinierte Verhalten abfangen" bis "alles tun, was nicht verboten ist".

Einige Beispiele für Code sind in Ordnung, bis ein aggressiv optimierender Compiler in die Finger kommt:

  • Überlauf prüfen

    // fails because the overflow test gets removed
    if (ptr + len < ptr || ptr + len > max) return EINVAL;
    
  • Überlaufartithmetik überhaupt verwenden:

    // The compiler optimizes this to an infinite loop
    for (i = 1; i > 0; i += i) ++j;
    
  • Speicher für sensible Informationen löschen:

    // the compiler can remove these "useless writes"
    memset(password_buffer, 0, sizeof(password_buffer));
    

Das Problem hier ist, dass Compiler seit Jahrzehnten weniger aggressiv in der Optimierung waren, und so lernen Generationen von C-Programmierern Dinge wie das Hinzufügen von festen Zweierkomplikationen und deren Überlauf. Dann wird der Standard der C-Sprache von Compiler-Entwicklern geändert, und die subtilen Regeln ändern sich, obwohl sich die Hardware nicht ändert. Die C-Sprachspezifikation ist ein Vertrag zwischen den Entwicklern und Compilern, aber die Vertragsbedingungen können sich im Laufe der Zeit ändern und nicht jeder versteht jedes Detail oder stimmt zu, dass die Details sogar sinnvoll sind.

Aus diesem Grund bieten die meisten Compiler Flags zum Deaktivieren (oder Einschalten) von Optimierungen an. Ist Ihr Programm mit dem Verständnis geschrieben, dass ganze Zahlen überlaufen können? Dann sollten Sie Überlaufoptimierungen deaktivieren, da diese Fehler verursachen können. Verhindert Ihr Programm strikt Aliasing-Zeiger? Dann können Sie die Optimierungen aktivieren, die davon ausgehen, dass Zeiger niemals verfälscht werden. Versucht Ihr Programm, den Speicher zu löschen, um Datenlecks zu vermeiden? In diesem Fall haben Sie kein Glück: Entweder müssen Sie die Entfernung von totem Code deaktivieren, oder Sie müssen vorab wissen, dass Ihr Compiler Ihren "toten" Code entfernen wird und einige Arbeit benötigen -und um es herum. 

Ja absolut.
Siehe hier , hier (was noch existiert - "von Design"!?!), hier , hier , hier , hier ...

Wenn ein Fehler durch Deaktivieren von Optimierungen beseitigt wird, ist es meistens immer noch deine Schuld

Ich bin für eine kommerzielle App verantwortlich, die hauptsächlich in C++ geschrieben wurde - angefangen mit VC5, früh auf VC6 portiert, jetzt erfolgreich auf VC2008 portiert. Sie ist in den letzten 10 Jahren auf über 1 Million Zeilen angewachsen. 

In dieser Zeit konnte ich einen einzelnen Codegenerierungsfehler bestätigen, der bei aktivierten aggressiven Optimierungen auftrat.

Warum beschwere ich mich? Denn in der gleichen Zeit gab es Dutzende von Fehlern, die mich am Compiler zweifeln ließen - aber es stellte sich heraus, dass ich den C++ - Standard nicht ausreichend verstanden habe. Der Standard schafft Raum für Optimierungen, die der Compiler möglicherweise verwendet.

Im Laufe der Jahre habe ich in verschiedenen Foren viele Posts gesehen, die den Compiler beschuldigten, was sich letztendlich als Fehler im ursprünglichen Code herausstellte. Zweifellos verdecken viele von ihnen Fehler, die ein detailliertes Verständnis der in der Norm verwendeten Konzepte benötigen, aber dennoch Quellcode-Fehler. 

Warum ich so spät antworte: Hören Sie auf, den Compiler zu beschuldigen, bevor Sie bestätigt haben, dass der Compiler tatsächlich daran schuld ist. 

24
peterchen

Compiler (und Laufzeit) -Optimierung kann sicherlich unerwünschtes Verhalten verursachen - aber es sollte mindestens nur dann geschehen, wenn Sie sich auf ein nicht festgelegtes Verhalten verlassen (oder tatsächlich falsche Annahmen über ein genau festgelegtes Verhalten treffen).

Darüber hinaus können Compiler natürlich auch Fehler enthalten. Einige davon können Optimierungen betreffen, und die Implikationen könnten sehr subtil sein - in der Tat sind dies wahrscheinlich, da offensichtliche Fehler eher behoben werden.

Unter der Annahme, dass Sie JITs als Compiler verwenden, habe ich in veröffentlichten Versionen sowohl der .NET-JIT- als auch der Hotspot-JVM Bugs gesehen (leider habe ich derzeit keine Details), die in besonders ungewöhnlichen Situationen reproduzierbar waren. Ob sie aufgrund bestimmter Optimierungen waren oder nicht, weiß ich nicht.

11
Jon Skeet

So kombinieren Sie die anderen Beiträge:

  1. Compiler haben gelegentlich Fehler in ihrem Code, wie die meisten Softwareprogramme. Das Argument "kluge Leute" ist völlig irrelevant, da NASA-Satelliten und andere Apps, die von intelligenten Leuten erstellt wurden, auch Fehler aufweisen. Die Codierung, die die Optimierung durchführt, unterscheidet sich von der Codierung, die nicht verwendet wird. Wenn sich also der Fehler im Optimierer befindet, kann der optimierte Code Fehler enthalten, der nicht optimierte Code jedoch nicht.

  2. Wie Herr Shiny und New hervorgehoben haben, ist es möglich, dass Code, der in Bezug auf Parallelität und/oder Zeitprobleme naiv ist, ohne Optimierung zufriedenstellend ausgeführt werden kann. Die Optimierung schlägt jedoch fehl, da dies den Ausführungszeitpunkt ändern kann. Sie könnten ein solches Problem für den Quellcode verantwortlich machen, aber wenn es sich nur bei einer Optimierung manifestiert, können einige Leute die Optimierung dafür verantwortlich machen.

9
Carl Smotricz

Nur ein Beispiel: Vor ein paar Tagen hat jemand entdeckt dieses gcc 4.5 mit der Option -foptimize-sibling-calls (was durch -O2 impliziert wird) eine ausführbare Emacs-Datei erstellt, die beim Start segfaults.

Dies wurde seitdem scheinbar behoben .

7
legoscia

Ich habe noch nie von einem Compiler gehört, dessen Direktiven das Verhalten eines Programms nicht ändern konnten. Im Allgemeinen ist dies eine gute Sache , aber Sie müssen das Handbuch lesen.

UND ich hatte kürzlich eine Situation, in der eine Compiler-Direktive einen Fehler "entfernt" hat. Natürlich ist der Fehler immer noch vorhanden, aber ich habe eine temporäre Problemumgehung, bis ich das Programm richtig behoben habe.

Ja. Ein gutes Beispiel ist das doppelt geprüfte Verriegelungsmuster. In C++ gibt es keine Möglichkeit, doppelt gesicherte Sperren sicher zu implementieren, da der Compiler Anweisungen in einer Weise ordnen kann, die in einem Single-Threaded-System sinnvoll ist, nicht aber in einem Multi-Threaded-System. Eine vollständige Diskussion finden Sie unter http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

6
tloach

Ist es wahrscheinlich Nicht in einem Hauptprodukt, aber es ist sicherlich möglich. Compiler-Optimierungen werden Code generiert; Egal woher der Code kommt (Sie schreiben oder etwas generiert), er kann Fehler enthalten.

5
Adam Robinson

Dies ist mir ein paar Mal mit einem neueren Compiler begegnet, der alten Code erstellt. Der alte Code würde funktionieren, stützte sich jedoch in einigen Fällen auf ein undefiniertes Verhalten, z. B. auf eine nicht ordnungsgemäß definierte Überlastung des Operators. Es würde in VS2003 oder VS2005 Debug Build funktionieren, aber in Release würde es abstürzen.

Beim Öffnen der Assembly wurde deutlich, dass der Compiler gerade 80% der Funktionalität der fraglichen Funktion entfernt hatte. Das Umschreiben des Codes, um nicht definiertes Verhalten nicht zu verwenden, löschte den Code.

Offensichtlicheres Beispiel: VS2008 vs GCC

Erklärt:

Function foo( const type & tp ); 

Namens:

foo( foo2() );

dabei gibt foo2() ein Objekt der Klasse type zurück.

In GCC stürzt tendenziell ab, da das Objekt in diesem Fall nicht auf dem Stack zugewiesen wird. VS führt jedoch einige Optimierungen durch, um dies zu umgehen, und es wird wahrscheinlich funktionieren.

5
peter karasev

Aliasing kann bei bestimmten Optimierungen zu Problemen führen. Aus diesem Grund haben Compiler die Option, diese Optimierungen zu deaktivieren. Aus Wikipedia :

Um solche Optimierungen auf vorhersagbare Weise zu ermöglichen, gibt der ISO-Standard für die Programmiersprache C (einschließlich der neueren C99-Edition) an, dass es mit einigen Ausnahmen illegal ist, dass Zeiger unterschiedlichen Typs auf denselben Speicherplatz verweisen. Diese Regel, die als "striktes Aliasing" bezeichnet wird, ermöglicht beeindruckende Leistungssteigerungen [Zitieren erforderlich], es ist jedoch bekannt, dass sie ansonsten gültigen Code bricht. Einige Softwareprojekte verstoßen absichtlich gegen diesen Teil des C99-Standards. Beispielsweise implementierte Python 2.x die Referenzzählung [1] und erforderte Änderungen an den grundlegenden Objektstrukturen in Python 3, um diese Optimierung zu ermöglichen. Der Linux-Kernel führt dies aus, da das strikte Aliasing Probleme bei der Optimierung von Inline-Code verursacht. [2] In solchen Fällen wird beim Kompilieren mit gcc die Option -fno-strict-aliasing aufgerufen, um unerwünschte oder ungültige Optimierungen zu verhindern, die möglicherweise falschen Code erzeugen.

4
Mark Ransom

Ja, Compiler-Optimierungen können gefährlich sein. Normalerweise verbieten harte Echtzeit-Softwareprojekte Optimierungen aus genau diesem Grund. Kennen Sie überhaupt eine Software ohne Fehler?

Aggressive Optimierungen können Ihre Variablen zwischenspeichern oder sogar seltsame Annahmen treffen. Das Problem liegt nicht nur in der Stabilität Ihres Codes, sondern sie können auch Ihren Debugger täuschen. Ich habe mehrmals gesehen, dass ein Debugger den Speicherinhalt nicht darstellt, weil einige Optimierungen einen variablen Wert in den Registern des Mikros beibehalten haben

Das Gleiche kann mit Ihrem Code passieren. Die Optimierung fügt eine Variable in ein Register ein und schreibt nicht in die Variable, bis sie abgeschlossen ist. Stellen Sie sich nun vor, wie unterschiedlich die Dinge sein können, wenn Ihr Code Zeiger auf Variablen in Ihrem Stack hat und mehrere Threads vorhanden sind

3
SystematicFrank

Es ist theoretisch möglich, sicher. Wenn Sie jedoch nicht darauf vertrauen, dass die Tools das tun, was sie tun sollen, warum sollten Sie sie dann verwenden? Aber sofort, jeder aus der Position von

"Compiler werden von intelligenten Leuten gebaut. und machen kluge Dinge" und können so. niemals falsch gehen.

macht ein dummes Argument.

Also, bis Sie Grund zu der Annahme haben, dass ein Compiler dies tut, warum sollten Sie sich darum kümmern? 

2
Daniel DiPaolo

Es kann vorkommen. Es hat sogar Linux betroffen.

2
Jean Azzopardi

Ich stimme sicher zu, dass es dumm ist zu sagen, weil Compiler von "intelligenten Leuten" geschrieben werden, dass sie daher unfehlbar sind. Kluge Leute entwarfen auch den Hindenberg und die Tacoma Narrows Bridge. Auch wenn es stimmt, dass Compiler-Schreiber zu den klügsten Programmierern auf dem Markt gehören, gilt auch, dass Compiler zu den komplexesten Programmen auf dem Markt gehören. Natürlich haben sie Fehler.

Andererseits zeigt die Erfahrung, dass die Zuverlässigkeit kommerzieller Compiler sehr hoch ist. Ich hatte viele Male, dass mir jemand gesagt hat, der Grund, warum Programm nicht funktioniert, MUSS wegen eines Fehlers im Compiler sein. und dann stellen wir fest, dass das Programm tatsächlich einen Fehler hat und nicht der Compiler. Ich versuche an Zeiten zu denken, bei denen ich persönlich auf etwas gestoßen bin, von dem ich wirklich überzeugt war, dass es ein Fehler im Compiler war, und ich kann mich nur an ein Beispiel erinnern.

Also generell: Vertrauen Sie Ihrem Compiler. Aber irren sie sich jemals? Sicher.

2
Jay

Wie ich mich erinnere, hatte das frühe Delphi 1 einen Fehler, bei dem die Ergebnisse von Min und Max umgekehrt wurden. Es gab auch einen verdeckten Fehler mit einigen Gleitkommawerten, wenn der Gleitkommawert innerhalb einer DLL verwendet wurde. Zugegeben, es ist mehr als ein Jahrzehnt vergangen, so dass mein Gedächtnis etwas unscharf ist.

1
Mike Chess

Die Compiler-Optimierung kann ruhende (oder versteckte) Fehler in Ihrem Code aufdecken (oder aktivieren). Es gibt möglicherweise einen Fehler in Ihrem C++ - Code, den Sie nicht kennen, den Sie einfach nicht sehen. In diesem Fall handelt es sich um einen versteckten oder ruhenden Fehler, da der Code-Zweig nicht [ausreichend oft] ausgeführt wird.

Die Wahrscheinlichkeit eines Fehlers in Ihrem Code ist viel größer (tausendfach höher) als ein Fehler im Code des Compilers: Weil die Compiler ausgiebig getestet werden. Von TDD plus praktisch von allen Menschen, die sie seit ihrer Freilassung verwenden!). Es ist also praktisch unwahrscheinlich, dass ein Fehler von Ihnen entdeckt wird und nicht buchstäblich Hunderttausende Male von anderen Menschen entdeckt wird.

Ein ruhender Fehler versteckter Fehler ist nur ein Fehler, der sich dem Programmierer noch nicht offenbart hat. Leute, die behaupten können, dass ihr C++ - Code keine (verborgenen) Fehler enthält, sind sehr selten. Es erfordert C++ - Kenntnisse (von denen nur wenige behaupten können) und ausführliche Tests des Codes. Es geht nicht nur um den Programmierer, sondern um den Code selbst (den Entwicklungsstil). Fehleranfällig ist der Charakter des Codes (wie streng getestet wird) oder/und der Programmierer (wie diszipliniert sich im Test befindet und wie gut C++ und Programmierung sind).

Sicherheits- und Parallelitätsfehler: Dies ist noch schlimmer, wenn wir Parallelität und Sicherheit als Fehler einschließen. Aber das sind doch "Fehler". Das Schreiben eines Codes, der in Bezug auf Parallelität und Sicherheit an erster Stelle fehlerfrei ist, ist nahezu unmöglich. Aus diesem Grund gibt es immer einen Fehler im Code, der bei der Compiler-Optimierung aufgedeckt (oder vergessen) werden kann.

1
Sohail Si

Ich habe in .NET 3.5 ein Problem gehabt, wenn Sie mit Optimierung bauen, eine andere Variable zu einer Methode hinzufügen, die ähnlich wie eine vorhandene Variable desselben Typs im selben Gültigkeitsbereich benannt wird zur Laufzeit gültig sein und alle Verweise auf die ungültige Variable werden durch Verweise auf die andere ersetzt.

Wenn ich beispielsweise abcd vom Typ MyCustomClass und abcd vom Typ MyCustomClass habe und abcd.a = 5 und abdc.a = 7 setze, haben beide Variablen die Eigenschaft a = 7. Um das Problem zu beheben, sollten beide Variablen entfernt, das Programm kompiliert werden (hoffentlich ohne Fehler) und dann sollten sie erneut hinzugefügt werden.

Ich denke, ich bin ein paar Mal mit .NET 4.0 und C # auf dieses Problem gestoßen, als ich Silverlight-Anwendungen durchführte. Bei meinem letzten Job stießen wir in C++ oft auf das Problem. Möglicherweise hat die Kompilierung 15 Minuten gedauert, sodass nur die benötigten Bibliotheken erstellt wurden. Manchmal war der optimierte Code jedoch genau derselbe wie beim vorherigen Build, obwohl neuer Code hinzugefügt und keine Build-Fehler gemeldet wurden.

Ja, Code-Optimierer werden von intelligenten Leuten gebaut. Sie sind auch sehr kompliziert, so dass Fehler häufig auftreten. Ich empfehle, jede optimierte Version eines großen Produkts vollständig zu testen. In der Regel sind Produkte mit eingeschränktem Verwendungszweck keine vollständige Veröffentlichung wert. Sie sollten jedoch im Allgemeinen getestet werden, um sicherzustellen, dass sie ihre allgemeinen Aufgaben korrekt ausführen.

1
Trisped

Alles, was Sie sich mit einem Programm oder einem Programm vorstellen können, führt zu Fehlern.

0
Jay

Ich arbeite an einer großen Konstruktionsanwendung, und ab und zu sehen wir nur Abstürze und andere von Kunden gemeldete Probleme. Unser Code enthält 37 Dateien (von etwa 6000), bei denen wir dies am Anfang der Datei haben, um die Optimierung zu deaktivieren, um solche Abstürze zu beheben:

#pragma optimize( "", off)

(Wir verwenden Microsoft Visual C++, native, 2015, aber es gilt für fast jeden Compiler, außer vielleicht Intel Fortran 2016 Update 2, bei dem wir noch keine Optimierungen vorgenommen haben.)

Wenn Sie die Feedback-Site von Microsoft Visual Studio durchsuchen, finden Sie dort auch einige Optimierungsfehler. Wir protokollieren gelegentlich einige von uns (wenn Sie es leicht genug mit einem kleinen Codeabschnitt reproduzieren können und Sie sich die Zeit nehmen möchten), werden sie behoben, aber andere werden wieder eingeführt. lächelt

Compiler sind Programme, die von Menschen geschrieben wurden, und jedes große Programm enthält Fehler, vertrauen Sie mir dabei. Die Compiler-Optimierungsoptionen enthalten höchstwahrscheinlich Fehler, und das Aktivieren der Optimierung kann sicherlich zu Fehlern in Ihrem Programm führen.

0
BenV136

Weitere und aggressivere Optimierungen können aktiviert werden, wenn das von Ihnen kompilierte Programm über eine gute Testsuite verfügt. Dann ist es möglich, diese Suite auszuführen und etwas sicherer zu sein, dass das Programm korrekt funktioniert. Sie können auch eigene Tests vorbereiten, die genau zu denen passen, die Sie in der Produktion planen. 

Es ist auch wahr, dass jedes große Programm einige Fehler haben kann (und wahrscheinlich hat), unabhängig davon, welche Schalter Sie zum Kompilieren verwenden. 

0
h22