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
staticuint16_tg;
2
3
intmain(){
4
for(uint8_ti=0;i<20;i++){
5
++g;// <a>
6
VPORTC.DIR;// <1> OK
7
// VPORTC_DIR; // <2> suppresses optimization
8
}
9
}
1
main:
2
ldsr24,g;g_lsm.6,g
3
ldsr25,g+1;g_lsm.6,g
4
ldir18,lo8(20);ivtmp_2,
5
.L2:
6
inr19,0x8;vol.1_7,MEM[(structVPORT_t*)8B].DIR
7
subir18,lo8(-(-1));ivtmp_2,
8
cpser18,__zero_reg__;ivtmp_2,
9
rjmp.L2;
10
adiwr24,20;tmp49,
11
stsg,r24;g,tmp49
12
stsg+1,r25;g,tmp49
13
ldir24,0;
14
ldir25,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
ldir24,lo8(20);ivtmp_4,
3
.L2:
4
ldsr18,g;g,g
5
ldsr19,g+1;g,g
6
subir18,-1;tmp48,
7
sbcir19,-1;,
8
stsg,r18;g,tmp48
9
stsg+1,r19;g,tmp48
10
inr25,0x8;vol.1_7,MEM[(volatileuint8_t*)8B]
11
subir24,lo8(-(-1));ivtmp_4,
12
cpser24,__zero_reg__;ivtmp_4,
13
rjmp.L2;
14
ldir24,0;
15
ldir25,0;
16
ret
Das ist sehr merkwürdig.
Was geht da vor? (Ein aliasing Problem in der escape-analyse?)
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.
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
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.
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.
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
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.
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 …
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.
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:
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.
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.
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.
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.
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.
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
staticuint16_tcounter;
2
3
staticuint16_tcount_3(){
4
ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
5
returnACCESS_ONCE(counter);
6
}
7
}
8
staticvoidfunc(void){
9
for(uint8_ti=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.
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?
Ε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
staticuint16_tg;
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).
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.
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.
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.)
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.