Forum: Compiler & IDEs avr-gcc: direkter Register Zugriff vs structure-mapping und fehlende Optimierung?


von Wilhelm M. (wimalopaan)


Lesenswert?

Mir ist die Tage folgendes aufgefallen, was ich nicht erklären kann.

In folgendem simplen C-Code kann eigentlich der Zugriff auf `g` komplett 
weg-optimiert werden. Das tut gcc aber nur, wenn `g` definitiv 
sequentiell nicht geändert wird, etwa wenn bei (a) statt `++g;` dann 
`++g;--g;` geschrieben wird.

Aber das ist jetzt nicht der Punkt, wenngleich man es sich anders 
wünscht.

gcc zieht sinnvollerweise "nur" das Inkrement aus der Schleife und macht 
daraus ein `g += 20;` außerhalb der Schleife. Auch gut.
1
static uint16_t g; 
2
3
int main() {
4
    for(uint8_t i = 0; i < 20; i++) {
5
        ++g; // <a> 
6
        VPORTC.DIR; // <1> OK
7
//        VPORTC_DIR; // <2> suppresses optimization
8
    }
9
}
1
main:
2
lds r24,g        ;  g_lsm.6, g
3
lds r25,g+1      ;  g_lsm.6, g
4
ldi r18,lo8(20)  ;  ivtmp_2,
5
.L2:
6
in r19,0x8       ;  vol.1_7, MEM[(struct VPORT_t *)8B].DIR
7
subi r18,lo8(-(-1))      ;  ivtmp_2,
8
cpse r18,__zero_reg__    ;  ivtmp_2,
9
rjmp .L2         ;
10
adiw r24,20      ;  tmp49,
11
sts g,r24        ;  g, tmp49
12
sts g+1,r25      ;  g, tmp49
13
ldi r24,0                ;
14
ldi r25,0                ;
15
ret

Dies passiert aber nur, wenn der Zugriff auf das direction-register von 
PortC mit structure-mapping zugegriffen wird. Beim direkten Zugriff via 
VPORTC_DIR ist gcc nicht mehr in der Lage zu optimieren:
1
main:
2
ldi r24,lo8(20)  ;  ivtmp_4,
3
.L2:
4
lds r18,g        ;  g, g
5
lds r19,g+1      ;  g, g
6
subi r18,-1      ;  tmp48,
7
sbci r19,-1      ; ,
8
sts g,r18        ;  g, tmp48
9
sts g+1,r19      ;  g, tmp48
10
in r25,0x8       ;  vol.1_7, MEM[(volatile uint8_t *)8B]
11
subi r24,lo8(-(-1))      ;  ivtmp_4,
12
cpse r24,__zero_reg__    ;  ivtmp_4,
13
rjmp .L2         ;
14
ldi r24,0                ;
15
ldi r25,0                ;
16
ret

Das ist sehr merkwürdig.
Was geht da vor? (Ein aliasing Problem in der escape-analyse?)

von c-hater (Gast)


Lesenswert?

Wilhelm M. schrieb:

> Dies passiert aber nur, wenn der Zugriff auf das direction-register von
> PortC mit structure-mapping zugegriffen wird. Beim direkten Zugriff via
> VPORTC_DIR ist gcc nicht mehr in der Lage zu optimieren:
[...]
> Das ist sehr merkwürdig.
> Was geht da vor? (Ein aliasing Problem in der escape-analyse?)

Mein Gott: Sieh' einfach ein, dass Compiler potentiell suboptimal sind. 
Es ist, das theoretisch völlig zweifelsfrei nachweisbar, völlig 
unmöglich, einen zu schreiben, der in jedem Kontext den optimalen Code 
erzeugt, denn so ein Compiler hätte eine quasi "unendliche" Komplexität, 
würde also niemals ein Compilat liefern können.

Tatsächlich funktionieren Compiler nur deshalb, weil sie die Sache in 
begrenzte Häppchen mit entsprechend begrenzter Komplexität zerlegen. Und 
diese Häppchen können sie dann (mit ein wenig Glück) optimal umsetzen.

von c-lover (Gast)


Lesenswert?

Gelaber. Hilft nicht bei der Beantwortung der Frage.

von Oliver S. (oliverso)


Lesenswert?

c-lover schrieb:
> Gelaber. Hilft nicht bei der Beantwortung der Frage.

Außer für avr-gcc-Compilerbauer ist das aber die einzig richtige 
Antwort.
Es ist schlicht sinnlos, zu fragen, warum ein Compiler etwas so macht, 
wie er es macht.

Allenfalls ein bugreport könnte man erstellen, aber über die kurze 
Diskussion, ob das überhaupt ein bug ist, wird der auch nicht 
hinauskommen.

Oliver

von (prx) A. K. (prx)


Lesenswert?

Sowas ist kein Bug, denn das Verhalten ist korrekt. Man könnte 
versuchen, es als missed optimization einzutüten, wird aber vermutlich 
feststellen, dass es einen Grund gibt, der für den Anwender des 
Compilers nicht offensichtlich ist.

C-hater hat insoweit Recht, als man von einem Compiler keine perfekte 
Optimierung etwarten kann. Korrektes Verhalten ist wichtiger.

Eine Spekulation dazu wäre, dass die Technik des Portzugriffs für den 
Compiler eine Absicherung gegen mögliches Aliasing auslöst. Er sich 
nicht völlig sicher ist, dass der Zauber hinter dem Portzugriff nicht 
vielleicht auf g zeigt.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

c-hater schrieb:
> Mein Gott: Sieh' einfach ein, dass Compiler potentiell suboptimal sind.

Du hast die Frage nicht verstanden! Ich bin ja mit der nicht so ganz 
vollständigen Optimierung des gcc im Falle (1) vollkommen zufrieden. Die 
Frage ist, warum wird das durch Version (2) verhindert.

Oliver S. schrieb:
> Es ist schlicht sinnlos, zu fragen, warum ein Compiler etwas so macht,
> wie er es macht.

Nein. Es ist wichtig zu verstehen: im Grunde ist die ein Schulbeispiel 
für einen Kurs im Compilerbau. Diesen Test besteht der gcc ja, wenn auch 
nicht vollständig. Die eigentliche Frage ist ja eine andere (s.o.).

(prx) A. K. schrieb:
> Sowas ist kein Bug, denn das Verhalten ist korrekt.

Es ist ganz klar ein missing-optimization-bug.

(prx) A. K. schrieb:
> Eine Spekulation dazu wäre, dass die Technik des Portzugriffs für den
> Compiler eine Absicherung gegen mögliches Aliasing auslöst.

Die Vermutung hatte ich ja oben auch schon (s. Eröffnungspost). Ich habe 
es auch schon mit einem restrict-qualified Zeiger versucht - auch ohne 
Erfolg.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Es ist ganz klar ein missing-optimization-bug.

Dann schreib halt den bugreport, und schau dir an, was der bewirkt.
Ich sach mal, du wirst damit auf wenig Verständnis stoßen.

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Letztendlich geht es um den Unterschied zwischen VPORTC_DIR und 
VPORTC.DIR in Bezug auf die Optimierung. Falls mir das jemand erklären 
kann, wäre ich schon zufrieden.

von Andreas M. (amesser)


Lesenswert?

Und wir sollen jetzt raten, was sich hinter den beiden Ausdrücken 
verbirgt?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Sind halt Compiler-Interna. Noch dazu wurde das gesamte Backend in GCC 
10 (meiner Erinnerung nach) komplett umgebaut. Du kannst also 
spaßeshalber auch mal einen (etwas) älteren GCC als Vergleich 
heranziehen. In vielen Fällen ist der alte Compiler besser, Johann hatte 
seinerzeit viel am AVR-Backend gemacht. Andererseits war das ganze 
Backend von vornherein (wenn ich mich recht erinnere) ein wenig 
"verkorkst", weil es einen fiktiven 16-Bit-Prozessor implementiert hat, 
was zu anderen missed optimizations geführt hat. Insofern bringt das 
neue Backend an einigen Stellen Optimierungen zustande, die das alte 
nicht recht konnte.

Vielleicht meldet sich Johann ja hier noch rein …

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> In folgendem simplen C-Code

Die erste Frage ist ja bei dir immer: C oder C++ ?

Oliver

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> Die erste Frage ist ja bei dir immer: C oder C++ ?

Da er das "void" in der Argumentliste weggelassen hat: C++. Spielt aber 
hier keine Geige, ist reproduzierbar (auch mit C). Ist ja eh eine 
Backend-Frage und unabhängig vom Frontend.

von Wilhelm M. (wimalopaan)


Lesenswert?

Andreas M. schrieb:
> Und wir sollen jetzt raten, was sich hinter den beiden Ausdrücken
> verbirgt?

Wer etwas mit AVR zu tun hat, der weiß was sich dahinter verbirgt.

Hier jetzt nochmal ausformuliert:
1
typedef struct C {
2
    volatile uint8_t r; 
3
} C_t;
4
5
#define VC (*(C_t *) 0x0008) 
6
7
#define C_r  (*(volatile uint8_t *)(0x0008))
8
9
static uint16_t g; 
10
11
int main() {
12
    for(uint8_t i = 0; i < 20; i++) {
13
        ++g;
14
//        VC.r; // <1> OK
15
        C_r; // <2> suppresses optimization
16
//        VPORTC.DIR; // <1> OK
17
//        VPORTC_DIR; // <2> suppresses optimization
18
    }
19
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> Wilhelm M. schrieb:
>> In folgendem simplen C-Code
>
> Die erste Frage ist ja bei dir immer: C oder C++ ?

Wilhelm M. schrieb:
> In folgendem simplen C-Code kann eigentlich der Zugriff auf `g` komplett
> weg-optimiert werden.

von Wilhelm M. (wimalopaan)


Lesenswert?

Jörg W. schrieb:
> Ist ja eh eine
> Backend-Frage und unabhängig vom Frontend.

Ich denke nicht, dass es eine Frage des Backends ist, sondern spätestens 
im RTL-Optimizer behandelt werden sollte.

von Wilhelm M. (wimalopaan)


Lesenswert?

Jörg W. schrieb:
> Sind halt Compiler-Interna. Noch dazu wurde das gesamte Backend in GCC
> 10 (meiner Erinnerung nach) komplett umgebaut. Du kannst also
> spaßeshalber auch mal einen (etwas) älteren GCC als Vergleich
> heranziehen.

Bingo.
In gcc-4.5.4 existiert das Problem nicht, jedoch ab gcc-4.6.4 schon.

Ist also noch etwas älter und hat - wie schon gesagt - nichts mit dem 
AVR-Backend zu tun, sondern etwas mit dem Middle-End (bis RTL). Daher 
tritt es bei allen Targets auf, nicht nur beim AVR.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Daher tritt es bei allen Targets auf, nicht nur beim AVR.

Wenn du das natürlich auf einem amd64-Target nachvollziehbar machen 
kannst, hat ein "missed optimization" Bugreport gute Chancen, dass sich 
dessen jemand annimmt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Die Frage ist, warum wird das durch Version (2) verhindert.

Ein Anfang wäre, den Pass zu finden, in dem die Optimierung nicht 
stattfindet. Dazu compilieren mit -fdump-tree-all -fdump-ipa-all 
-fdump-rtl-all und die Dump-Dateien untersuchen und feststellen, was in 
(1) anders läuft als in (2).  Vermutlich ist es einer der Tree-Passes.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> Wilhelm M. schrieb:
>> Die Frage ist, warum wird das durch Version (2) verhindert.
>
> Ein Anfang wäre, den Pass zu finden, in dem die Optimierung /nicht/
> stattfindet. Dazu compilieren mit -fdump-tree-all -fdump-ipa-all
> -fdump-rtl-all und die Dump-Dateien untersuchen und feststellen, was in
> (1) anders läuft als in (2).  Vermutlich ist es einer der Tree-Passes.

Danke: werde ich mir auch mal bei Gelegenheit ansehen.

Wie groß die Auswirkungen in der Praxis sind, ist fraglich. Jedenfalls 
trifft es natürlich auch solche Sachen wie:
1
static uint16_t counter;
2
3
static uint16_t count_3() {
4
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
5
        return ACCESS_ONCE(counter);
6
    }
7
}
8
static void func(void) {
9
    for(uint8_t i = 0; i < 20; i++) {
10
        g += count_3();
11
    }
12
}

Da ich jedoch denke, dass oft an den betroffenen Stellen auch noch 
non-inline Funktionsaufrufe enthalten sind und nicht mit LTO gearbeitet 
wird, sind die Auswirkungen begrenzt.

Anders sieht es allerdings aus, wenn man z.B. alle Registerzugriffe 
konsequent über structure-mapping macht und auch sonst alles inline ist 
(weil etwa alles als templates in header-only Bibliotheken drin ist), 
weil bspw. eben C++ verwendet. Jedenfalls ist es mir so im Vergleich von 
meinem C++-Code zu einer reinen C-Implementierung aufgefallen, dass in C 
schlechter optimiert werden konnte.

von Εrnst B. (ernst)


Lesenswert?

Wilhelm M. schrieb:
> Jedenfalls
> trifft es natürlich auch solche Sachen wie:

"ATOMIC_BLOCK" beinhaltet eine Optimization/Compiler/Memory Barrier. Der 
Compiler darf den Wert von "counter" nicht raus- oder reinziehen.

Keine Ahnung was das "ACCESS_ONCE" darin noch macht?

von Wilhelm M. (wimalopaan)


Lesenswert?

Εrnst B. schrieb:
> Wilhelm M. schrieb:
>> Jedenfalls
>> trifft es natürlich auch solche Sachen wie:
>
> "ATOMIC_BLOCK" beinhaltet eine Optimization/Compiler/Memory Barrier. Der
> Compiler darf den Wert von "counter" nicht raus- oder reinziehen.

Ja, klar.
Aber darum geht es doch in diesem Beispiel gar nicht.

Hier geht es darum, dass in ATOMIC_BLOCK ja zweimal der Zugriff auf das 
Status-Register mit dem Macro SREG stattfindet. Und weil das so ist und 
mit dem Fehler in gcc ab 4.6.4. findet keine Optimierung bzgl. der 
globalen Variable `g` mehr statt. Ups, sehe gerade, dass ich die im 
obigen Beispiel irgendwie nicht copy-n-pasted habe. Also denke Dir bitte 
eine Zeile
1
static uint16_t g;

noch dazu.

> Keine Ahnung was das "ACCESS_ONCE" darin noch macht?

Das ist wie im Linux-Kernel der lvalue-volatile-cast (hier streng 
genommen wegen der memory-barrier unnötig). Allerdings ist es ja leider 
so, dass die globale MemoryBarrier (oder auch ggf. ein Output-Clobber 
bzgl. counter hier) fälschlicherweise auch eine Optimierung bzgl. der 
globalen Variable `g` verhindert. Also, insgesamt 3 Fehler im aktuellen 
gcc, wobei jeder für sich schon die Generierung eines optimalen Code 
verhindert.
Workaround ist hier, auf den direkten Register-Zugriff zu verzichten und 
stattdessen das structure-mapping einzusetzen, und auch auf die 
ATOMIC_BLOCK-Macros zu verzichten, weil die ja ebenfalls direkt auf SREG 
zugreifen und zudem eine globale Memory-Barrier benutzen.
In C++ benutze ich schon immer ein structure-mapping mit eingenen 
Register-Typen, so dass auch nur die richtigen Flags für die Register 
benutzt werden können, ansonsten Compile-Zeit-Fehler, und andererseits 
auch schon immer eine immer gleich aufgebaute Klasse für die ISRs mit 
den nebenläufig verwendeten Variablen ohne eine Memory-Barrier, sondern 
explizitem volatile-access außerhalb der ISR. Das führt immer zu einem 
optimalen Ergebnis auch in Gegenwart der drei gcc Fehler. Das habe ich 
auch so nicht gewusst: Glück gehabt ;-)

Wohlgemerkt, dass sind Fehler im Middle-End von gcc und haben m.E. 
nichts mit dem AVR Backend zu tun (im avr-gcc fällt es aber eben am 
meisten auf).

von c-hater (Gast)


Lesenswert?

Wilhelm M. schrieb:

[viel Kram]
> Wohlgemerkt, dass sind Fehler im Middle-End von gcc und haben m.E.
> nichts mit dem AVR Backend zu tun (im avr-gcc fällt es aber eben am
> meisten auf).

Naja, wer AVR8 mit C oder gar C++ benutzt, dem kommt es wohl sowieso 
nicht auf optimale Performance an.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

c-hater schrieb:
> dem kommt es wohl sowieso nicht auf optimale Performance an.

Es mag ja Dinge geben, von denen du Ahnung hast … C auf AVRs gehört 
nicht dazu.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

c-hater schrieb:
> dem kommt es wohl sowieso nicht auf optimale Performance an.

Es mag ja Dinge geben, von denen du Ahnung hast … C auf AVRs gehört 
nicht dazu. (Davon abgesehen, dass wir ja schon festgestellt haben, dass 
das hier nicht AVR-spezifisch ist.)

von Wilhelm M. (wimalopaan)


Lesenswert?

Jörg W. schrieb:
> (Davon abgesehen, dass wir ja schon festgestellt haben, dass
> das hier nicht AVR-spezifisch ist.)

Evtl. macht es Sinn, den Titel des Threads zu ändern. Auf der anderen 
Seite ist das wohl auch vergeblich: zwar tritt das bei allen Targets 
auf, doch dort "interessiert" es ggf. weniger, bei den kleinen AVRs 
fällt es einfach mehr ins Gewicht.
Die BugReports für den gcc sind auch erstellt.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.