Warum lässt der GCC-Compiler Code weg?

9

Ich kann nicht verstehen, warum der GCC-Compiler einen Teil meines Codes ausschneidet, während er absolut den gleichen in der Nachbarschaft beibehält.

Der C-Code:

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

Der entsprechende Teil von LSS (Assembler-Datei, vom Compiler erstellt):

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

Ich könnte annehmen, dass der Compiler herausfindet, dass ein solcher Code ein Dummy ist, und ihn ausschneidet, aber warum wird derselbe am Ende des Codes beibehalten?

Gibt es Compiler-Anweisungen, um eine solche Optimierung zu verhindern?

Roman Matveev
quelle
1
Sie können den Compiler auch anweisen, keine einzelne Funktion zu optimieren. Vielleicht lohnt es sich, diese Methode mit Ihrem ISR auszuprobieren. Siehe diese Frage zum Stackoverflow.
Vladimir Cravero
2
Hey Roman, ich habe deiner Frage das "c" -Tag hinzugefügt, um atmega zu entfernen. Ich musste ein Tag entfernen, da es ein Limit gibt (fünf), und wenn ich eine codebezogene Frage stelle, ist das Hinzufügen des Sprachnamens als Tag großartig, da der gesamte Code (Fragen und Antworten) hervorgehoben wird.
Vladimir Cravero
5
Im Allgemeinen sind übergeordnete Sprachen (wie C) explizit so konzipiert, dass sie nicht an eine 1: 1-Beziehung zu ihrer resultierenden Assembly gebunden sind. Wenn Sie Anweisungen zählen müssen, um das richtige Timing zu erreichen, müssen Sie sich immer auf die Montage verlassen (wie einige der Antworten). Der springende Punkt bei Hochsprachen ist, dass der Compiler die Freiheit hat, Ihren Code schneller, stärker und besser als zuvor zu machen. Details wie Registerzuordnungen und Verzweigungsvorhersagen werden viel besser dem Compiler überlassen ... außer in Zeiten wie diesen, in denen Sie als Programmierer genau die gewünschten Anweisungen kennen.
Cort Ammon
5
Die bessere Frage ist, warum GCC diese beiden Schleifen nicht optimiert .
Ilmari Karonen
5
Gcc rollt zuerst die Schleife ab und bemerkt erst dann, dass der entsprechende Code unbrauchbar ist. Mit einer Schleifengröße von 30 wäre das Abrollen dumm und gcc tut es nicht. Auf einer höheren Optimierungsstufe werden beide weg optimiert.
Marc Glisse

Antworten:

9

Da Sie in einem Kommentar angeben, dass "jeder CPU-Tick wert ist", schlage ich vor, eine Inline-Assembly zu verwenden, um Ihre Verzögerungsschleife so zu gestalten, wie Sie es möchten. Diese Lösung ist den verschiedenen überlegen volatileoder -O0weil sie klar macht , was Ihre Absicht ist.

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

Das sollte den Trick machen. Das Flüchtige ist da, um dem Compiler zu sagen: "Ich weiß, dass dies nichts bewirkt, behalte es einfach und vertraue mir." Die drei asm "-Anweisungen" sind ziemlich selbsterklärend. Sie können jedes Register anstelle von r24 verwenden. Ich glaube, der Compiler mag niedrigere Register, daher möchten Sie möglicherweise ein hohes verwenden. Nach dem ersten :sollten Sie die Ausgabevariablen (Lesen und Schreiben) c :auflisten , und es gibt keine. Nach dem zweiten sollten Sie die Eingabevariablen (nur) auflisten. Wiederum gibt es keine, und der dritte Parameter ist eine durch Kommas getrennte Liste der geänderten Register in diesem Fall r24. Ich bin mir nicht sicher, ob Sie auch das Statusregister aufnehmen sollten, da sich das ZEROFlag natürlich ändert, ich habe es nicht aufgenommen.

Bearbeiten Sie die bearbeitete Antwort wie gewünscht. Einige Notizen.

Die "+rm"vor (i)bedeutet , dass Sie den Compiler einlassen entscheiden i in platzieren m Emory oder in einem r egister. Das ist in den meisten Fällen eine gute Sache, da der Compiler besser optimieren kann, wenn er kostenlos ist. In Ihrem Fall glaube ich, dass Sie nur die r-Einschränkung beibehalten möchten, um i zu zwingen, ein Register zu sein.

Vladimir Cravero
quelle
Sieht so aus, als ob ich das wirklich brauche. Aber können Sie Ihre Antwort so ändern, dass sie eine cVariable anstelle des 10in der ursprünglichen Antwort erwähnten Literals akzeptiert ? Ich versuche, die GCC-Handbücher über die ordnungsgemäße Verwendung der ASM- Konstruktion zu lesen, aber sie sind für mich jetzt etwas verdeckt. Ich würde mich sehr freuen!
Roman Matveev
1
@ RomanMatveev bearbeitet, wie Sie angefordert haben
Vladimir Cravero
13

Sie könnten versuchen, die Schleife tatsächlich dazu zu bringen, etwas zu tun. So wie es aussieht, sagt der Compiler zu Recht: "Diese Schleife macht nichts - ich werde sie loswerden."

Sie können also ein Konstrukt ausprobieren, das ich häufig verwende:

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

Hinweis: Nicht alle Ziele für den gcc-Compiler verwenden dieselbe Inline-Assemblysyntax. Möglicherweise müssen Sie sie für Ihr Ziel anpassen.

Majenko
quelle
Ihre Lösung scheint viel eleganter zu sein als meine ... Vielleicht ist meine besser, wenn eine genaue Zykluszählung erforderlich ist? Ich meine, es ist nicht garantiert, dass das Ganze für eine bestimmte Art und Weise kompiliert wird, oder?
Vladimir Cravero
8
Die einfache Tatsache, dass C verwendet wird, bedeutet, dass Sie keine Zyklen garantieren können. Wenn Sie eine genaue Zykluszählung benötigen, ist ASM der einzige Weg. Ich meine, Sie erhalten ein anderes Timing mit der C-Schleife, wenn Sie -funroll-Schleifen aktiviert haben, als wenn Sie dies nicht tun usw.
Majenko
Ja das ist, was ich dachte. Wenn Sie HW-Verzögerungen mit ausreichend hohen i-Werten (100 oder mehr) durchführen, liefert Ihre Lösung vermutlich praktisch die gleichen Ergebnisse und verbessert gleichzeitig die Lesbarkeit.
Vladimir Cravero
6

Ja, das könnte man annehmen. Wenn Sie die Variable i als flüchtig deklarieren, weisen Sie den Compiler an, i nicht zu optimieren.

Ambiorix
quelle
1
Das stimmt meiner Meinung nach nicht ganz.
Vladimir Cravero
1
@VladimirCravero, was meinst du damit? Könnten Sie das klarer machen?
Roman Matveev
2
Was ich damit meinte ist, dass ich mir nicht so sicher wäre, was ein Compiler macht. Wenn Sie eine Variable als flüchtig deklarieren, wird der Compiler darüber informiert, dass sie sich möglicherweise an einer anderen Stelle ändert.
Vladimir Cravero
1
@ Roman Matveev register unsigned char volatile i __asm__("r1");vielleicht?
a3f
2
iAls flüchtig zu deklarieren löst alles. Dies wird durch den C-Standard 5.1.2.3 garantiert. Ein konformer Compiler darf diese Schleifen nicht optimieren, wenn sie iflüchtig sind. Glücklicherweise ist GCC ein konformer Compiler. Es gibt leider viele potenzielle C-Compiler, die nicht dem Standard entsprechen, aber das ist für diese spezielle Frage irrelevant. Es ist absolut kein Inline-Assembler erforderlich.
Lundin
1

Nach der ersten Schleife iist eine Konstante. Die Initialisierung von iund die Schleife erzeugen lediglich einen konstanten Wert. Nichts im Standard legt fest, dass diese Schleife unverändert kompiliert werden muss. Der Standard sagt auch nichts über das Timing aus. Der kompilierte Code muss sich so verhalten, als ob die Schleife vorhanden wäre und dies auch tut. Sie können nicht zuverlässig feststellen, dass diese Optimierung unter dem Standard durchgeführt wurde (Timing zählt nicht).

Die zweite Schleife sollte ebenfalls gelöscht werden. Ich halte es für einen Fehler (oder eine fehlende Optimierung), der es nicht ist. Nachdem die Schleife ikonstant Null ist. Der Code sollte durch Setzen iauf Null ersetzt werden.

Ich denke, GCC bleibt inur aus dem Grund bestehen, dass Sie einen (undurchsichtigen) Portzugriff durchführen, der sich möglicherweise auswirkt i.

Benutzen

asm volatile ("nop");

GCC dazu zu bringen zu glauben, dass die Schleife etwas tut.

usr
quelle