Hi! Kann man den avr-g++ optimizer dazu bringen, ein Objekt, das "static const" deklariert ist, nicht im Speicher anzulegen, sondern den (konstanten) Inhalt des Objekts stattdessen quasi "inline" zu verwenden? Im Anhang ist ein Beispiel, wozu das gut sein soll (ich bin gerade dabei zu evaluieren, wie man mit C++ für Atmel µC sauberen Code schreiben kann). Das Beispiel zeigt eine Klasse "PortPin", die einen Pin eines I/O-Ports repräsentiert und das entsprechende Bit setzen bzw. löschen kann. Wenn man den Code mit avr-g++ -mmcu=atmega640 -O3 -S port.cc -o port.s compiliert, bleibt von der test()-Funktion nur sbi 34-32,3 cbi 34-32,3 ret übrig, weniger geht nicht. Nun wäre es aber wünschenswert, alle benötigten Port Pins in einem Header als "static const" Objekte zu definieren, also z.B. static const PortPin LED1(PORTA, 0); static const PortPin LED2(PORTA, 1); // ... Wenn man das macht, erhält man jedoch wesentlich mehr Code, die Test-Funktion im obigen Beispiel wird dann zu lds r30,ppa3 lds r31,(ppa3)+1 ld r24,Z lds r25,ppa3+2 or r24,r25 st Z,r24 lds r30,ppa3 lds r31,(ppa3)+1 ld r25,Z lds r24,ppa3+2 com r24 and r24,r25 st Z,r24 ret Außerdem wird Code erzeugt, um das Objekt im Speicher zu initialisieren. Eigentlich müsste der Compiler aber erkennen können, dass der Inhalt des Objekts konstant ist, und ähnlich optimalen Code wie oben erzeugen. Meine Frage ist daher, ob es irgendeine Compiler-Option (oder eine andere Formulierung im Code) gibt, damit auch mit "static const" Objekten optimaler Code erzeugt wird. Mir ist klar, dass man mit dem Präprozessor etwas Ähnliches tun kann, z.B. #define LED1 PortPin(PORTA, 0) #define LED2 PortPin(PORTA, 1) // ... aber mir geht es hier darum auszuloten, wie weit man mit den Sprachelementen von C++ (und der optimizer-Implementierung im avr-g++) kommt. Hat jemand eine Idee dazu? Danke & schöne Grüße, Markus
Markus Grabner schrieb: > Wenn man den Code mit > > avr-g++ -mmcu=atmega640 -O3 -S port.cc -o port.s > > compiliert, bleibt von der test()-Funktion nur > > sbi 34-32,3 > cbi 34-32,3 > ret > > übrig, weniger geht nicht. Ich hatte versehentlich die Datei angehängt, die den langen Code erzeugt. Den kurzen Code (die drei Zeilen oben) erhält man mit der Datei "port_optimal.cc". Schöne Grüße, Markus
Markus Grabner schrieb: > aber mir geht es hier darum auszuloten, wie weit man mit den > Sprachelementen von C++ (und der optimizer-Implementierung im avr-g++) > kommt. Das Problem ist, dass der Compiler zwar erkennen kann dass solche Variablen konstant sind und wegoptimiert werden können, aber das setzt sich erstmal nicht auf deren Membervariablen fort. Seit C++11 gibt es aber den constexpr Mechanismus: Deklariere die Variablen statt "static const" als "constexpr", deklariere den Konstruktur als constexpr, und alle Memberfunktionen als "const" (das stellt bestimmte Anforderungen an den Code, siehe Google). Dem GCC muss man noch sagen dass er C++11 verwenden soll, durch die Compileroption -std=c++11. Dazu sollte man mindestens Version 4.7.3 verwenden. Eventuell muss man noch ein bisschen nachhelfen den Compiler zu überzeugen keine expliziten Funktionsaufrufe zu generieren, indem man die betreffenden Funktionen (d.h. Konstruktur & Member-Funktionen) mit "inline __attribute__((always_inline))" deklariert. Dann sollte der Compiler beim Aufruf der Funktionen den Inhalt der Membervariablen kennen und das zusammen wegoptimieren.
PS: noch ein paar Tips: Anstelle von die "PORTx" Variable als Referenz in die Klasse zu übernehmen würde ich einen uint8_t der die Portnummer angibt verwenden (0 => PORTA, 1 => PORTB etc.), und dann "live" in den set/clear Methoden etc. die Adressen von den entsprechenden Registern PORTx, DDRx, PINx berechnen. Wenn du die Instanzen dann als Konstanten verwendest wie jetzt wird das onehin komplett wegoptimiert, und so kannst du aber auch Instanzen an Funktionen übergeben (die dann ja nur 2 Bytes groß sind) wo dann "inline" eine Adressberechnung stattfindet (Dynamik/Flexibilität vs. Performance). Die Übergabe sollte dabei ruhig by-Value stattfinden, das ist bei derart kleinen Klassen effizienter als by-Reference, da der zusätzliche Dereferenzierungsschritt wegfällt, die Übergabe aber dennoch effizient ist (direkt in Registern). Die Klasse PortPin ist dann quasi selber eine Referenz auf die entsprechenden Hardwareregister, und kann daher praktisch wie ein Pointer behandelt werden (wie eben Übergabe by-Value).
Dr. Sommer schrieb: > Markus Grabner schrieb: >> aber mir geht es hier darum auszuloten, wie weit man mit den >> Sprachelementen von C++ (und der optimizer-Implementierung im avr-g++) >> kommt. > Seit C++11 gibt es aber den constexpr Mechanismus: Deklariere die > Variablen statt "static const" als "constexpr", deklariere den > Konstruktur als constexpr, und alle Memberfunktionen als "const" Super Tip, das war's schon! Die modifizierte Version ist wieder angehängt. Ich habe den gcc gleich mal von 4.3.3 auf 4.8.2 aktualisiert, der ist jetzt auch so freundlich, die Port-Adresse für den Assembler schon auszurechnen, also sieht die test()-Funktion jetzt so aus: sbi 0x2,3 cbi 0x2,3 ret Irgendwie schräg, dass man ausgerechnet auf einem Mikrocontroller C++11-Features benötigt, um optimalen Code zu generieren :-) Vielen Dank & schöne Grüße, Markus
Dr. Sommer schrieb: > Die Übergabe sollte dabei ruhig by-Value stattfinden, das ist bei derart > kleinen Klassen effizienter als by-Reference, da der zusätzliche > Dereferenzierungsschritt wegfällt, die Übergabe aber dennoch effizient > ist (direkt in Registern). Grundsätzlich stimme ich zu, aber nachdem der Compiler/Optimizer mit der Referenz klarkommt, gefällt es mir hier aus Gründen der Lesbarkeit besser, direkt die PORTx-Konstanten zu verwenden. Wer weiß, was PORTA (und constexpr :-) bedeutet, wird vermutlich auch constexpr PortPin LED1(PORTA, 0); verstehen, ohne in der Dokumentation nachlesen zu müssen. Schöne Grüße, Markus
Das muss am Datentyp "PortPin" liegen[1]. Definiere ich im IAR (für den MSP) einen "const char" (int, long etc) landet der ordnungsgemäß im Flash und wird nicht in den RAM gespiegelt. [1]Das Dilemma wird wohl das wüste Durcheinander von Makros und Variablen sein, welches AVR mit seinen Headern ausliefert.
Markus Grabner schrieb: > Grundsätzlich stimme ich zu, aber nachdem der Compiler/Optimizer mit der > Referenz klarkommt, gefällt es mir hier aus Gründen der Lesbarkeit > besser, direkt die PORTx-Konstanten zu verwenden. Das "Problem" tritt halt auf wenn du außer dem PORTx Register noch DDRx und PINx hinzufügst, und eine PortPin Instanz in einem Kontext verwendest, in dem der Compiler deren Inhalt nicht kennt (zB. Funktions-Parameter oder allgemein veränderbare Variable). Dann wird die komplette Instanz inkl. 3er Referenzen und Bitmaske kopiert & übergeben bzw. gespeichert. Das verwenden der Nummer würde diesen Speicher einsparen. Markus Grabner schrieb: > Wer weiß, was PORTA > (und constexpr :-) bedeutet, ... richtig. Aber das kann man wiederum ausgleichen über eine vordefinierte Factory-Klasse und entsprechende Instanzen:
1 | class PortFactory { |
2 | public:
|
3 | inline constexpr PortFactory (uint8_t nPort) : m_nPort (nPort) {} |
4 | inline constexpr PortPin operator [] (uint8_t i) const { return {m_nPort, 1 << i}; } |
5 | protected:
|
6 | uint8_t m_nPort; |
7 | };
|
8 | |
9 | constexpr PortFactory PORTA (0); |
10 | constexpr PortFactory PORTB (1); |
11 | constexpr PortFactory PORTC (2); |
12 | |
13 | constexpr PortPin LED1 { PORTC[3] }; |
14 | |
15 | int main () { |
16 | PORTA [0].set (); |
17 | PORTB [5].clear (); |
18 | LED1.set (); |
19 | }
|
Die PORTA,B,C,LED1 Instanzen werden hier natürlich wieder wegoptimiert, falls möglich. Markus Grabner schrieb: > Irgendwie schräg, dass man ausgerechnet auf einem Mikrocontroller > C++11-Features benötigt, um optimalen Code zu generieren :-) Warum nicht, in C++ werden neue innovative Features zur Effizienz-Verbesserung auch bei abstrakteren Programmen (wie eben mit eigenen Klassen) eingebaut. u.a. dank constexpr eignet sich C++ umso mehr für Mikrocontroller. Roland schrieb: > Definiere ich im IAR (für den MSP) einen "const char" (int, long etc) > landet der ordnungsgemäß im Flash und wird nicht in den RAM gespiegelt. Ja, genau das ist das Problem. Die Konstante soll direkt in die Instruktionen (sbi etc) encodiert werden, und nicht als Extra-Konstante im Flash (oder RAM) die erst per "lpm"("lds") geladen werden müsste. Roland schrieb: > Das muss am Datentyp "PortPin" liegen[1]. Den hat er selbst definiert. Roland schrieb: > [1]Das Dilemma wird wohl das wüste Durcheinander von Makros und > Variablen sein, welches AVR mit seinen Headern ausliefert. Das Problem ist der alte C++03 Standard, welcher kein constexpr kennt und somit keine Möglichkeit, Compile-Time-Konstanten dem Compiler & Optimizer bekannt zu machen. Dass es in der 1. Version der "port_optimal.cc" "richtig" funktionierte ware pure Freundlichkeit vom GCC, das so zu optimieren.
Dr. Sommer schrieb: > ... > > Roland schrieb: >> Definiere ich im IAR (für den MSP) einen "const char" (int, long etc) >> landet der ordnungsgemäß im Flash und wird nicht in den RAM gespiegelt. > Ja, genau das ist das Problem. Die Konstante soll direkt in die > Instruktionen (sbi etc) encodiert werden, und nicht als > Extra-Konstante im Flash (oder RAM) die erst per "lpm"("lds") geladen > werden müsste. > ... Da der (embedded-)Kern (welcher auch immer) sowieso alles was er macht erst mal aus dem Speicherort in irgend welche Arbeitsregister laden muss, verstehe ich dein Problem nicht. Es ist völlig Latte, ob lpm die Arbeitsdaten aus einer Flash-Zelle oder Ram-Zelle holt. Es dauert immer einen Takt. Das ist der Preis der Harvard-Architektur. Dem OP ging es doch nur darum, das "doppelte" Laden vom Flash in eine Ram-Zelle und dann erst in die Arbeitsregister zu vermeiden. Zumindest habe ich es so interpretiert. Denn alle weiterführenden Optimierungsversuche scheitern an der Arbeitsweise der CPUs.
Du willst doch jetzt nicht behaupten, daß der Flash- oder gar RAM-Verbrauch egal wären und man nicht drauf achten sollte?
Wenn man die volle Optimierung vom Kompiler zulässt: Ja, es ist egal, denn der Kompiler wird das gewünschte Optimum (Größe/Geschwindigkeit) rausholen. Wieviel man letztlich braucht, entscheidet der Programmierer, wie "verschwenderisch" er mit Variablen umgeht. Wenn ich keine Variablen anlege, brauche ich auch keinen RAM. Das eigentliche Problem des OP entsteht aber eh aus der Plattform AVR, da der Kopfstände machen muss, um Daten aus dem Programmspeicher auch als Daten verarbeiten zu können. Andere Plattformen brauchen diese Aufstände nicht. Dort reicht tatsächlich ein "static const" um Ram zu sparen. Mehr Flash braucht man dabei auch nicht, denn alle Konstanten, auch wenn sie später im RAM liegen müssen ja trotzem erst mal im Flash liegen.
Roland schrieb: > Da der (embedded-)Kern (welcher auch immer) sowieso alles was er macht > erst mal aus dem Speicherort in irgend welche Arbeitsregister laden > muss, verstehe ich dein Problem nicht. Das Laden direkt aus einem Opcode geht schneller als das Laden per lpm. > Es ist völlig Latte, ob lpm die > Arbeitsdaten aus einer Flash-Zelle oder Ram-Zelle holt. Bis darauf dass lpm ausschließlich auf dem Flash funktioniert, und nicht mit dem RAM. > Es dauert immer einen Takt. Das ist der Preis der Harvard-Architektur. Nein, LPM braucht 3 Zyklen, LD 2. Roland schrieb: > Dem OP ging es doch nur darum, das "doppelte" Laden vom Flash in eine > Ram-Zelle und dann erst in die Arbeitsregister zu vermeiden. Es ging ihm hauptsächlich darum, die Zieladresse in den Opcode zu encodieren, anstelle sie als seperate Konstante im Flash zu haben. Roland schrieb: > Denn alle weiterführenden > Optimierungsversuche scheitern an der Arbeitsweise der CPUs. Wie man sieht kann man die CPU mehr oder weniger optimal nutzen. Zur Verdeutlichung etwas Code (modulo Syntaxfehler): Dies passiert, wenn die Portadresse & Pinmaske als Konstanten im Flash liegen und der Compiler sie beim Pinsetzen nicht kennt - er muss sie immer extra laden:
1 | .CSEG |
2 | ; globale "static const" Variablen im Flash |
3 | PORTADRESSE: .DW PORTA ; Portadresse im Flash |
4 | PINMASK: .DB 2 ; Pinmaske im Flash |
5 | ... |
6 | |
7 | ; Code zum Setzen der Bits anhand der Variablen im Flash |
8 | ldi zl, low(PORTADRESSE) ; Lade Portadresse ... |
9 | ldi zh, high(PORTADRESSE) |
10 | lpm yl, Z+ |
11 | lpm yh, Z+ |
12 | |
13 | ldi zl, low(PINMASK) ; Lade Pinmaske... |
14 | ldi zh, high(PINMASK) |
15 | lpm r17, Z |
16 | ld r18, Y ; Lade PORT-Wert |
17 | or r18, r17 ; Setze Maske |
18 | st r18, Y ; Setze PORT-Wert |
Wenn dem Compiler aber hingegen die Inhalte der Portadresse & Pinmaske zum Zeitpunkt des Setzens bekannt sind, kann er diese direkt in die Instruktion encodieren und folgendes generieren:
1 | sbi PORTA, 1 |
Wenn man genau hinsieht kann man einen Größen&Laufzeit -Unterschied erkennen, obwohl beides Male die Portadresse & Pinmaske aus dem Flash stammen. Roland Ertelt schrieb: > Wenn man die volle Optimierung vom Kompiler zulässt: Ja, es ist egal, > denn der Kompiler wird das gewünschte Optimum (Größe/Geschwindigkeit) > rausholen. Das kann er hier nicht, wenn er den Port nicht kennt! Dann muss er ihn "dynamisch" aus der Konstanten-Tabelle (im Flash) laden (siehe 1. Beispiel). Roland Ertelt schrieb: > Wieviel man letztlich braucht, entscheidet der Programmierer, > wie "verschwenderisch" er mit Variablen umgeht. Richtig - ohne constexpr wird eine konstante Variable im Flash angelegt die extra geladen werden muss. > Wenn ich keine Variablen > anlege, brauche ich auch keinen RAM. ... oder Flash; und genau darum gehts bei constexpr, das Anlegen einer expliziten konstanten Variablen (ja, so heißt das in C, C++) zu verhindern und stattdessen die Daten direkt in den Opcode zu integrieren. Roland Ertelt schrieb: > Das eigentliche Problem des OP entsteht aber eh aus der Plattform AVR, > da der Kopfstände machen muss, um Daten aus dem Programmspeicher auch > als Daten verarbeiten zu können. Diese Plattform macht den unoptimierten Code besonders hässlich, ja - aber das Ursprungsproblem kommt aus der Compiler&Sprach -Logik. Roland Ertelt schrieb: > Dort reicht tatsächlich ein "static const" um Ram zu > sparen. Er möchte aber auch Flash sparen. > Mehr Flash braucht man dabei auch nicht, denn alle Konstanten, > auch wenn sie später im RAM liegen müssen ja trotzem erst mal im Flash > liegen. Wenn sie aber in der Instruktion liegen (siehe 2. Beispiel) brauchen sie weniger Flash.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.