Hallo,
Ich verwende avr-g++ 4.3.5
ich habe eine Klasse, die einen Port abstrahieren soll.
Die Klasse hat ein Attribut
1
volatileuint8_t*constport_address
und eine Funktion
1
inlineboolPort::get_pin(uint8_tpin_number)const
2
{
3
boolresult=*port_address_&(1<<pin_number);
4
5
returnresult;
6
}
Die Funktion ist in einem Header-File
Ich würde erwarten, dass hier mit -Os optimiert wird zu
1
sbis [port_address], [pin_number]
stattdessen bekomme ich (für pin_number = 4)
1
in r24,55-32
2
lsr r24
3
lsr r24
4
lsr r24
5
sbrc r24,0
Es scheint also, dass der Compiler korrekt inferiert, aber nicht die
AVR-spezifische Optimierung anwendet.
Kompiliere ich die Funktion außerhalb einer Klasse, also z.B. mit einer
globalen port-Variable und rufe diese Funktion dann mit pin_number als
Parameter auf, funktioniert alles wie erwartet.
Wie kann ich nun den Compiler dazu bringen, die Optimierung anzuwenden?
tschüss
Ich habe mit einer normalen Variable, mit einer const-Variable und mit
einer Zahl/Immediate (das dürfte ja das gleiche wie eine konstante
Variable als Parameter sein.)
Ich möchte die Funktion möglichst so halten, dass ich eine Konstante
übergeben kann, also z.B.
1
MyPort.get_pin(5)
als auch dynamisch, also zum Beispiel in einer for-Schleife aufrufen.
Der Compiler hat zu entscheiden, was passiert. Der statische Fall sollte
aber natürlich nicht langsamer sein, als
1
if(PORT.x&(1<<pin_number))
. Klar das ist eigentlich nichts anders. Aber in einem anderen Kontext
übersetzt der Compiler das in ein sbis mit nachfolgendem conditional
branch, hier aber komischerweise nicht. Ich meine zwei shifts nach
links, ein logisches AND und ein test auf null sind genau das gleiche,
aber ein sbis + branch sind natürlich weniger Instruktionen. Ich frage
mich also, warum optimiert der Compiler hier nicht bis zum Ende?
Ich habe auch mal zum Test den Parameter für get_bit als const
deklariert; das sollte ja auf jeden Fall abhelfen, aber falsch gedacht.
Rolfs Frage gilt immer noch. Wenn der Compiler die Funktion so aufrufen
muss, dass sie zwingend einen Wert 0/1 liefert, dann ist es Essig mit
der von dir gewünschten Optimierung. Deshalb ebenso wichtig, wie die
Funktion aufgerufen wird, wie deren Inhalt.
Sinnvollerweise baust du ein minimiertes(!) vollständiges Testbeispiel,
dass idealerweise ohne zusätzliche Files compilierbar ist.
Ich verstehe nicht, was damit gemeint ist
A. K. schrieb:> Wenn der Compiler die Funktion so aufrufen> muss, dass sie zwingend einen Wert 0/1 liefert
Meinst du damit, dass der Compiler nichts optimiert, sondern quasi den
Rückgabewert in r24 lässt (siehe assembly unten) und dann der Prolog und
alles durch das Inlining wegfällt?
Hier mein Code (ich habe den andern Krempel einfach mal weggelassen):
http://pastebin.com/ucNJfnw4
Diese Beispiel kompiliert zu:
1
main:
2
in r24,55-32
3
lsr r24
4
lsr r24
5
lsr r24
6
sbrc r24,0
7
rjmp .L2
8
cbi 55-32,2
9
.L3:
10
.L5:
11
rjmp .L5
12
.L2:
13
sbi 55-32,2
14
rjmp .L3
Dieses Beispiel hingegen:
1
#include<avr/io.h>
2
3
volatileuint8_t*constport=&PORTB;
4
5
inlineboolfunc(uint8_tx)
6
{
7
return*port&(1<<x);
8
}
9
10
intmain(void)
11
{
12
uint8_tx=4;
13
if(func(x))
14
{
15
*port=0xFF;
16
}
17
18
while(true);
19
return0;
20
}
Kompiliert zu:
1
main:
2
sbis 56-32,4
3
rjmp .L2
4
ldi r24,lo8(-1)
5
out 56-32,r24
6
.L2:
7
.L4:
8
rjmp .L4
Ich verstehe den Unterschied nicht. In beiden Fällen ist das
PORT-register durch einen const pointer auf volatile uint8_t gegeben und
der pin wird als (const) parameter übergeben.
Wo ist nun die Grenze für die Optimierung? Die Inline Funktionen sind
übrigens in den *_impl.h Header-Dateien definiert, also sollte es keine
"Übersetzungseinheitsgrenzen" geben
holger schrieb:> Versuchs doch mal so:>> get_pin(1 << 4)>>> bool result = *port_address_ & pin_number;
Das liefert mir tatsächlich ein sbic.
Wodran liegt das? In dem Beispiel das ich in diesem Beitrag gegeben
habe, wird ja auch nur die Pinnummer übergeben und es wird zu einem
sbis.
häh?
Könnte sein, dass der interessante Teil genau der ist:
// hier wird geschaut, ob der Button aktiviert ist,
// oder sich der Zustand des Buttons geändert hat...
Weil der Port volatile ist muss der Zugriff darauf davor erfolgen. Da
bleibt dem Compiler meist nichts übrig, als sich den Stand in einem
Register zu merken. Das Flag-Register hält nicht lang vor.
A. K. schrieb im Beitrag #2228283:
> Wie bitteschön soll der Compiler beiinline void Button::Poll()> ein sbic/sbis produzieren, wo darin ausdrücklich der Portzustand mit> einer Variablen verglichen wird?
active_high_ ist als const bool deklariert, damit sollte es den
Unterschied zwischen sbic und sbis machen, sonst nichts.
Aber wie gesagt, wenn ich
einsetze, (parameter geändert) und get_pin anpasse:
1
inlineboolPort::get_pin(uint8_tpin_number)const
2
{
3
boolresult=*port_address_&(pin_number);
4
5
returnresult;
6
}
,dann bekomme ich mein sbic.
Wo liegt nun der unterschied, ob ich (1 << 3) übergebe, oder 1 und dann
in der inline-Funktion (1 << pin_number) mache?
Nur die Pin-Nummer zu übergeben ist natürlich mein Ziel, da es logischer
und bequemer ist.
A. K. schrieb:> Könnte sein, dass der interessante Teil genau der ist:> // hier wird geschaut, ob der Button aktiviert ist,> // oder sich der Zustand des Buttons geändert hat...> Weil der Port volatile ist muss der Zugriff darauf davor erfolgen. Da> bleibt dem Compiler meist nichts übrig, als sich den Stand in einem> Register zu merken. Das Flag-Register hält nicht lang vor.
Dieser Teil sieht so aus:
1
boolactive=ButtonB2.is_active();
2
boolhas_changed=ButtonB2.has_changed();
3
boolactivated=ButtonB2.activated();
4
booldeactivated=ButtonB2.deactivated();
5
6
boolx=(active&&has_changed&&activated);
7
8
PortB.change_pin(2,x);
einfach irgendein Schwachsinn, damit nichts wegoptimiert wird.
Da hast du doch die Antwort. Die diversen Port-Tests lassen sich anders
als sonstige Dinge nicht umordnen und dorthin verzögern wo wirklich
benötigt, weil volatile. Daher finden die in der gegebenen Reihenfolge
an der Stelle statt, wo sie im Quelltext stehen, und landen somit
zwangsläufig als Wert in Registern.
Bei if(func(...)) entsteht das Problem nicht, weil die Verwendung des
Ergebnisses direkt auf den Test folgt.
PS: Bei den const propagations hast du recht, daher hatte ich den Kram
bereits gelöscht.
A. K. schrieb:> Da hast du doch die Antwort. Die diversen Port-Tests lassen sich anders> als sonstige Dinge nicht verzögern, weil volatile. Daher finden die in> der gegebenen Reihenfolge statt und landen somit zwangsläufig als Wert> in Registern.
Also, übergebe ich (1<<3) bekomme ich das sbic, übergebe nur die Drei
(als inhalt einer konstanten Variable, erhalte ich folgendes assembly:
1
in r24,55-32
2
lsr r24
3
lsr r24
4
lsr r24
5
sbrc r24,0
6
rjmp .L2
7
cbi 55-32,2
8
.L3:
9
.L5:
10
rjmp .L5 ;bla bla und so weiter
11
.L2:
12
sbi 55-32,2
13
rjmp .L3
Drei mal left shift und dann wird gebrancht wenn der Inhalt vom Bit ganz
rechts gleich 0 ist. Das kann doch ohne Probleme zu einem sbis r24, 3
optimiert werden?
> PS: Bei den const propagations hast du recht, daher hatte ich den Kram> bereits gelöscht.
Habe, ich dann auch gesehen, ist ja kein Ding.
> Drei mal left shift und dann wird gebrancht wenn der Inhalt vom Bit ganz> rechts gleich 0 ist. Das kann doch ohne Probleme zu einem sbis r24, 3> optimiert werden?
Kann. Aber ich habe bisher keinen Quellcode gesehen, der einen Pin
ändert. NB: lsr schiebt rechts.
Wenn du dauernd unvollständige Codefragmente vorsetzt, dann artet das in
Glaskugelspiele aus. Hier fehlt jetzt vermutlich change_pin.
Bring mal Code, der compilierbar ist (linkfähig muss er nicht sein),
möglichst alles in einem File, keinerlei #includes (auch da ich grad
keine Standardincludes parat habe). Und lass alles weg was unnötig.
A. K. schrieb:> NB: lsr schiebt rechts.
Richtig, ich kann das nicht auseinanderhalten.
A. K. schrieb:> Aber ich habe bisher keinen Quellcode gesehen, der einen Pin> ändert.
Das soll ja auch extern geschehen.
A. K. schrieb:> Bring mal Code, der compilierbar ist (linkfähig muss er nicht sein),> möglichst alles in einem File, keinerlei #includes. Und lass alles weg> was unnötig.
Hier:
http://pastebin.com/rvxj8piZ
das Ergebnis ist bei mir (avr-g++ -S -Os -mmcu=atmega8 test.cc):
Bestätigt. Maske geht, Bit nicht.
Tja, da kann ich dich nur dafür beglückwünschen, dass es dir gelungen
ist, die Optimierung vom GCC auszuhebeln. Nobody is perfect, auch der
nicht.
>Wo liegt nun der unterschied, ob ich (1 << 3) übergebe, oder 1 und dann>in der inline-Funktion (1 << pin_number) mache?
Du hast einen variablen Shift, und das ist immer Mist.
holger schrieb:> Ja, für die 3. Gibt es auch ein Beispiel für 4 oder 5> zusätzlich im Code?
bis 3 wird lsr angewendet, dadrüber gibt es zuerst einen swap gefolgt
von lsr.
swap vertauscht die nibble.
holger schrieb:> Du hast einen variablen Shift, und das ist immer Mist.
Der Shift soll ja im Quelltext möglichst Variabel sein, so dass man die
Möglichkeit hat Pins variabel zu setzen (also beispielsweise, abhängig
von einer bestimmten Eingabe), oder eben statisch. Je nachdem was man
möchte hat man damit das gleiche Interface, aber jeweils das richtige
(statisch oder variabel) Resultat. Es gibt natürlich noch ca. 20 andere
Funktionen um mehrer Pins auf einmal zu setzen, oder dem Port-Register
einen anderen Zustand zuzuweisen, usw.
Ja, ist PR33049.
Wer besseren Code sehen will, muss sich also bis zu avr-gcc 4.7
gedulden. Leider sind solche Patches nicht gern gesehen, bisher ist es
noch nicht in GCC integriert. Gegebenenfalls wäre die erzeugte
Instruktionsfolge
Da du avr-gcc offenbar selbst generierst:
1. Ist der Code mit dem Patch wie erwartet?
2. configure im Quellverzeichnis oder einem Unterverzeichnis
davon wird nicht unterstützt. Rechne mit allen Sorten von
seltsamen Effekten:
http://gcc.gnu.org/install/configure.html
3. Ein Bugreport ohne Quellen ist nicht hilfreich, da niemand
das Problem nachvollziehen kann — allein aus dem Bugreport
noch nichtmal du selbst:
http://gcc.gnu.org/bugs/#need
Ich hatte nochmal etwas freie Zeit.
Johann L. schrieb:> 1. Ist der Code mit dem Patch wie erwartet?
Nein, genauso wie vorher. Allerdings habe ich gcc-4.6.0 kompiliert. Da
scheinen im avr.md file ca. 200 Zeilen zu fehlen. Ich werde 4.7 bei
Gelegenheit nochmal testen.
Johann L. schrieb:> 3. Ein Bugreport ohne Quellen ist nicht hilfreich, da niemand> das Problem nachvollziehen kann — allein aus dem Bugreport> noch nichtmal du selbst:> http://gcc.gnu.org/bugs/#need
Danke, kenne ich schon.
ffonsel schrieb:> Ich hatte nochmal etwas freie Zeit.>> Johann L. schrieb:>> 1. Ist der Code mit dem Patch wie erwartet?>> Nein, genauso wie vorher. Allerdings habe ich gcc-4.6.0 kompiliert.
Klar da ist die Äanderung ja auch nicht drinne. In 4.6 müsstest du die
paar Zeilen von Hand hinzufügen.
Johann L. schrieb:> Klar da ist die Äanderung ja auch nicht drinne. In 4.6 müsstest du die> paar Zeilen von Hand hinzufügen.
Ich habe schon nicht vergessen zu patchen ;)
ffonsel schrieb:> Johann L. schrieb:>> Klar da ist die Äanderung ja auch nicht drinne. In 4.6 müsstest du die>> paar Zeilen von Hand hinzufügen.>> Ich habe schon nicht vergessen zu patchen ;)
Auch neu generiert und installiert? *wegduck*
hmmm. Dann würd ich mir das nochmal anschauen falls das ii-File am
Bugreport hinge.
Ich habe gerade den HEAD von heute kompiliert, mit und ohne Patch keine
Änderung.
Ich fühle mich nicht danach, den bug zu filen, aber falls das jemand
machen möchte sind hier alle nötigen Information
test.ii (intermediate) http://pastebin.com/ZPV521si
test.s (ausgabe) http://pastebin.com/DDFW0FCU
compiler-output (verbose) http://pastebin.com/XHNPB6WX
Bedenke, daß ein vernünftiger Fehlerreport mit allen notwendigen
Informationen im Report notwendig ist, so daß überhaupt eine Arbeit an
dem Problem möglich ist.
GCC-Entwickler haben idR keine Lust und Zeit, jemand die notwendigen
Infos aus der Nase zu ziehen.
Johann L. schrieb:> Bedenke, daß ein vernünftiger Fehlerreport mit allen notwendigen> Informationen im Report notwendig ist, so daß überhaupt eine Arbeit an> dem Problem möglich ist.>> GCC-Entwickler haben idR keine Lust und Zeit, jemand die notwendigen> Infos aus der Nase zu ziehen.
Eigentlich hatte ich beim ersten Eintrag gedacht ich hätte alles
rangehangen, aber irgendwo hat der File upload nicht geklappt, war nicht
meine Absicht. Aber dann bei mir ist die frustgrenze dann meistens auch
schnell erreicht, weil ich gar nicht im GCC irgendwo rumwühlen will und
zwanzig Varianten ausprobieren, sondern ich will einfach nur "fertig
werden".. anyway jetzt ist es da.