Ich kann überhaupt nicht verstehen, was da los ist! Ich habe wenig Ahnung von Montage, aber ich erwartete so etwas wie
addl $2000000000, -8(%rbp)
Ich habe es sogar mit versucht gcc -c -g -Wa,-a,-ad -O2 test.c um den C-Code zusammen mit der Assembly zu sehen, in die er konvertiert wurde, aber das Ergebnis war nicht klarer als das vorherige.
Kann jemand kurz erklären:
Das gcc-S-O2 Ausgang.
Ist die Schleife so optimiert, wie ich es erwartet habe (eine Summe statt vieler Summen)?
Schöne Frage übrigens, und willkommen bei Stackoverflow! Dies ist ein schönes Beispiel für eine hervorragende erste Frage. 🙂
– Mystisch
12. Januar 2012 um 20:49 Uhr
Mystisch
Der Compiler ist sogar noch intelligenter. 🙂
Tatsächlich erkennt es, dass Sie das Ergebnis der Schleife nicht verwenden. Es hat also die gesamte Schleife vollständig entfernt!
#include <stdio.h>
int main(void) {
int i; int count = 0;
for(i = 0; i < 2000000000; i++){
count = count + 1;
}
// Print result to prevent Dead Code Elimination
printf("%d\n", count);
}
BEARBEITEN : Ich habe das Erforderliche hinzugefügt #include <stdio.h>; das MSVC-Assembly-Listing entspricht einer Version ohne die #includesollte aber gleich sein.
Ich habe GCC im Moment nicht vor mir, da ich in Windows gebootet habe. Aber hier ist die Demontage der Version mit der printf() auf MSVC:
EDIT: Ich hatte die falsche Assembly-Ausgabe. Hier ist die richtige.
Also ja, Visual Studio führt diese Optimierung durch. Ich würde davon ausgehen, dass GCC dies wahrscheinlich auch tut.
Und ja, GCC führt eine ähnliche Optimierung durch. Hier ist eine Assembly-Liste für dasselbe Programm mit gcc -S -O2 test.c (gcc 4.5.2, Ubuntu 11.10, x86):
Nun, ich fühle mich gerade wirklich dumm. Ich habe nicht an die Eliminierung von totem Code gedacht (eew.. wusste nicht). Ich habe es mit printf() und gcc versucht, und es erzeugt ziemlich denselben optimierten Code. Danke für deine Antwort!
– Heil
12. Januar 2012 um 20:45 Uhr
Fühlen Sie sich nicht dumm. So etwas ist überhaupt nicht offensichtlich, wenn Sie gerade erst in das Micro-Benchmarking einsteigen. Es ist nur ein Teil des Lernprozesses.
– Mystisch
12. Januar 2012 um 20:46 Uhr
Es wäre interessant zu wissen, wie der Compiler solche Entscheidungen trifft. Was wäre, wenn diese Schleife aus irgendeinem Grund wirklich gebraucht würde?
– ketschap
12. Januar 2012 um 21:39 Uhr
@marcushatchenson Das ist ein ziemlich großes Compiler-Thema. Die Grundidee ist, dass der Compiler a generiert Abhängigkeitsdiagramm, die dann verwendet werden können, um zu beweisen/widerlegen, ob bestimmte Berechnungen jemals benötigt werden. Dinge, die nachweislich nicht benötigt werden, werden dann eliminiert.
– Mystisch
12. Januar 2012 um 21:51 Uhr
@marcushatchenson – der einzige Effekt, den die Schleife hat, ist das Inkrementieren count, die eine lokale Variable ist. Die C-Spezifikation besagt, dass nichts außerhalb der Funktion das Lokale kennt und der Compiler weiß, dass die Funktion nichts mit dem Ergebnis macht. Nach den Regeln der Spezifikation gibt es keine möglichen Auswirkungen auf das Programm, wenn count wird nicht berechnet, daher darf der Optimierer sie verwerfen. Wenn Sie dagegen count als global deklarieren, muss der Compiler dies anders behandeln.
– Russell Borogove
13. Januar 2012 um 0:53 Uhr
Compiler haben ein paar Werkzeuge zur Verfügung, um Code effizienter oder “effizienter” zu machen:
Wenn das Ergebnis einer Berechnung nie verwendet wird, kann der Code, der die Berechnung durchführt, weggelassen werden (wenn die Berechnung ausgeführt wurde volatile Werte, müssen diese Werte trotzdem gelesen werden, aber die Ergebnisse des Lesens können ignoriert werden). Wenn die Ergebnisse der Berechnungen, die es gespeist haben, nicht verwendet wurden, kann der Code, der diese ausführt, ebenfalls weggelassen werden. Wenn eine solche Weglassung den Code für beide Pfade auf einer bedingten Verzweigung identisch macht, kann die Bedingung als unbenutzt angesehen und weggelassen werden. Dies hat keine Auswirkungen auf das Verhalten (außer der Ausführungszeit) von Programmen, die keine Speicherzugriffe außerhalb der Grenzen vornehmen oder das aufrufen, was Anhang L als “kritisches undefiniertes Verhalten” bezeichnen würde.
Wenn der Compiler feststellt, dass der Maschinencode, der einen Wert berechnet, nur Ergebnisse in einem bestimmten Bereich erzeugen kann, kann er alle bedingten Tests auslassen, deren Ergebnis auf dieser Grundlage vorhergesagt werden könnte. Wie oben, wirkt sich dies nicht auf andere Verhaltensweisen als die Ausführungszeit aus, es sei denn, der Code ruft “Critical Undefined Behaviors” auf.
Wenn der Compiler feststellt, dass bestimmte Eingaben irgendeine Form von undefiniertem Verhalten mit dem geschriebenen Code hervorrufen würden, würde der Standard dem Compiler erlauben, jeglichen Code wegzulassen, der nur relevant wäre, wenn solche Eingaben empfangen werden, selbst wenn das natürliche Verhalten der Ausführungsplattform Angesichts solcher Eingaben wäre dies harmlos gewesen, und das Umschreiben des Compilers würde es gefährlich machen.
Gute Compiler machen #1 und #2. Aus irgendeinem Grund ist #3 jedoch in Mode gekommen.
14144400cookie-checkWie optimiert GCC eine nicht verwendete Variable, die in einer Schleife inkrementiert wird?yes
Schöne Frage übrigens, und willkommen bei Stackoverflow! Dies ist ein schönes Beispiel für eine hervorragende erste Frage. 🙂
– Mystisch
12. Januar 2012 um 20:49 Uhr