PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [ERLEDIGT] Optimierung Arduino-IDE - Assembler



Moppi
03.10.2018, 07:48
Hallo!

Eine Frage an diejenigen, die sich besser mit ASM bei den Atmegas auskennen; ich habe zwar 8 Jahre in Assembler mein Unwesen getrieben, aber alles von 8086- bis 80486-Maschinen. Assembler bei den Mikrokontrollern ist ja doch wieder mal was ganz anderes. Und zwar habe ich beispielhaft folgende Codezeilen in der Arduino-IDE:



DDRD = B11111111;
PORTC = B00110000;
while(PINC&8){}


I/O-Ports beschreiben und lesen und eine while-Schleife, in der ein Port abgefragt und das dann logisch verknüpft wird, um auf ein Ereignis zu warten.

In wie weit wäre das denn mit ASM noch optimierbar, dahingehend, dass evtl. überflüssiger Code entfällt?
Mir ist noch nicht klar, ob und was für überflüssiger Code, bei der Umwandlung durch den Compiler - für obiges Codebeispiel - entsteht.

So weit ich mich bis heute damit auseinandergesetzt habe, stehe ich zurzeit auf dem Aussichtspunkt, dass Inline-Assembler für einzelne Portabfragen wenig Sinn macht, weil ja auch Parameter von und in den C-Code drumrum übergeben werden müssen, so dass dort auch wieder, für sehr kurze ASM-Sequenzen, betrachtlicher Overhead entsteht. Deshalb dürfte es dann wohl mehr Sinn machen, ganze Programmteile in ASM zu verfassen.

Bei obigem Codebeispiel denke ich mir bisher, dass hier das Maschinencode-Ergebnis vom Compiler doch schon recht kurz und knackig ausfallen müsste. Oder lohnt es sich auch solche Sequenzen zu optimieren?

MfG

Searcher
03.10.2018, 08:24
Bei obigem Codebeispiel denke ich mir bisher, dass hier das Maschinencode-Ergebnis vom Compiler doch schon recht kurz und knackig ausfallen müsste. Oder lohnt es sich auch solche Sequenzen zu optimieren?

Ich denke nicht, daß bei den drei gezeigten Zeilen Laufzeit mit inline asm eingespart werden könnte. Das sieht ja schwer nach C Anweisungen aus und nicht nach Arduino spezifischen Kommandos wie zB. DigitalRead() oder DigitalWrite(). Bei den "Arduino Kommandos" ließe sich einiges einsparen - liest man - ich bin ja kein Arduino User.

Ich lese mir im Zweifel die von der IDE erzeugte Hexdatei (hoffe die Arduino IDE erzeugt auch eine solche) in einen Disassembler ein und schaue mir die Unterschiede an, nachdem ich vorher die Stelle mit mehreren NOPs hintereinander markiert habe.

Gruß
Searcher

Klebwax
03.10.2018, 08:49
Typischerweise können C-Compiler einem ein Assemblerlisting mit dem C-Code als Kommentar liefern. Und wenn man Pech hat, erkennt man in stark optimiertem Code sein eigenes C-Programm nicht mehr wieder.

https://www.systutorials.com/240/generate-a-mixed-source-and-assembly-listing-using-gcc/

MfG Klebwax

Moppi
03.10.2018, 09:13
Ich habe mal weiter gesucht und bin drauf gestoßen, dass auch ASM-Befehle für das Abfragen einzelner Port-Bits existieren. Damit würde sich ein "PINC & 8" beispielsweise erübrigen und eine extra Abfrage drumrum nicht stattfinden müssen, die in einem Vergleich bestehen würde. SBIC und SBIS wären dafür ausreichend. Ich will erst mal herausfinden, was praktisch an Geschwindigkeit herauszuholen wäre. Optimierung ist am Ende ja immer fallspezifisch.

Searcher
03.10.2018, 09:33
dass auch ASM-Befehle für das Abfragen einzelner Port-Bits existieren. Damit würde sich ein "PINC & 8" beispielsweise erübrigen und eine extra Abfrage drumrum nicht stattfinden müssen, die in einem Vergleich bestehen würde. SBIC und SBIS wären dafür ausreichend.

Ein Compiler weiß das auch und verwendet die auch eventuell bei der Optimierung.

Ich habe auch schon mal fragliche Codestücke in I/O Pin-setzen und -löschen eingeschlossen und mit Oszilloskop die Laufzeit ausgemessen.

oberallgeier
03.10.2018, 09:37
.. Bei obigem Codebeispiel denke ich mir bisher, dass hier das Maschinencode-Ergebnis vom Compiler doch schon recht kurz und knackig ausfallen müsste. Oder lohnt es sich auch solche Sequenzen zu optimieren? ..Da stelln wa uns ma jaantz domm.

Und schauen uns die jeweilige *.lls an, nachdem wir in zwei unterschiedlichen Optimierungsstufen compiliert haben. Umgebung: AVRStudio4.18 Build 700, WinAVR-20100110, Win7pro. Die originalen Zeilen sind rot markiert - und an die Fehlermeldungen angepasst.

Optimiert mit -0s

arno_01-lss-Opt_-0s
Optimierung -0s
arno_01.elf: file format elf32-avr
//
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Izthrznt = 20000; // Der ZeitHorizont in ISR(TIMER2_COMPA_vect)
12e0: 80 e2 ldi r24, 0x20 ; 32
12e2: 9e e4 ldi r25, 0x4E ; 78
12e4: 90 93 40 04 sts 0x0440, r25
12e8: 80 93 3f 04 sts 0x043F, r24
Izeit_A = Izthrznt; //
12ec: 80 91 3f 04 lds r24, 0x043F
12f0: 90 91 40 04 lds r25, 0x0440
12f4: 90 93 33 04 sts 0x0433, r25
12f8: 80 93 32 04 sts 0x0432, r24
Isecundn = 0; // Sekundenzähler, max 9 Stunden - NUR hier nullen
12fc: 10 92 fd 03 sts 0x03FD, r1
1300: 10 92 fc 03 sts 0x03FC, r1
** ************************************************** ************************** */
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// DDRD = B11111111;
// PORTC = B00110000;
DDRD = 0B11111111;
1304: 8f ef ldi r24, 0xFF ; 255
1306: 8a b9 out 0x0a, r24 ; 10
PORTC = 0B00110000;
1308: 80 e3 ldi r24, 0x30 ; 48
130a: 88 b9 out 0x08, r24 ; 8
while(PINC&8){}
130c: 33 99 sbic 0x06, 3 ; 6
130e: fe cf rjmp .-4 ; 0x130c <main+0xb8>
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/* ************************************************** ************************** **
** ************************************************** ************************** */
// - - - - - - - - - - - - - - - -
Svpt = 0; // Servopointer, => ~tmr~/ISR (TIMER1_COMPA_vect)
1310: 10 92 20 04 sts 0x0420, r1
TC1TMR_init ( ); // OCR1AV und ~BV in ~com~ gesetzt
1314: 0e 94 d3 01 call 0x3a6 ; 0x3a6 <TC1TMR_init>
// - - - - - - - - - - - - - - - -

Optimiert mit -03 => der Code des >>gesamten Programms<< ist dabei ein vielfaches länger :-/

arno_01-lss-Opt_-03
Optimierung -03
arno_01.elf: file format elf32-avr

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Izthrznt = 20000; // Der ZeitHorizont in ISR(TIMER2_COMPA_vect)
b954: 80 e2 ldi r24, 0x20 ; 32
b956: 9e e4 ldi r25, 0x4E ; 78
b958: 90 93 40 04 sts 0x0440, r25
b95c: 80 93 3f 04 sts 0x043F, r24
Izeit_A = Izthrznt; //
b960: 80 91 3f 04 lds r24, 0x043F
b964: 90 91 40 04 lds r25, 0x0440
b968: 90 93 33 04 sts 0x0433, r25
b96c: 80 93 32 04 sts 0x0432, r24
Isecundn = 0; // Sekundenzähler, max 9 Stunden - NUR hier nullen
b970: 10 92 fd 03 sts 0x03FD, r1
b974: 10 92 fc 03 sts 0x03FC, r1
** ************************************************** ************************** */
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// DDRD = B11111111;
// PORTC = B00110000;
DDRD = 0B11111111;
b978: 4a b9 out 0x0a, r20 ; 10
PORTC = 0B00110000;
b97a: 80 e3 ldi r24, 0x30 ; 48
b97c: 88 b9 out 0x08, r24 ; 8
while(PINC&8){}
b97e: 33 99 sbic 0x06, 3 ; 6
b980: fe cf rjmp .-4 ; 0xb97e <main+0xd0>
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/* ************************************************** ************************** **
** ************************************************** ************************** */
// - - - - - - - - - - - - - - - -
Svpt = 0; // Servopointer, => ~tmr~/ISR (TIMER1_COMPA_vect)
b982: 10 92 20 04 sts 0x0420, r1
TC1TMR_init ( ); // OCR1AV und ~BV in ~com~ gesetzt
b986: 0e 94 3d 01 call 0x27a ; 0x27a <TC1TMR_init>
// - - - - - - - - - - - - - - - -

Moppi
03.10.2018, 10:48
AVRStudio4.18 Build 700, WinAVR-20100110: machen dann ja zumindest das, was man erwarten würde bzw. selber auch in ASM schreiben würde.

Searcher
03.10.2018, 11:03
Eine interessante Stelle ist die Verwendung von Interruptroutinen. Der Compiler rettet beim Aufruf meist mehr Register auf dem Stack als nötig.

HaWe
03.10.2018, 11:54
Eine interessante Stelle ist die Verwendung von Interruptroutinen. Der Compiler rettet beim Aufruf meist mehr Register auf dem Stack als nötig.

das stimmt, das ist auch der Grund, weshalb manche Intr nicht mehr korrekt funktionieren, wenn man direkt die GPIOs manipuliert:

direct port manipulation compiles to 1 or 2 instructions, calling digitalRead is dozens.
One issue to be aware of is interrupts - some of the native direct-port manipulation code will not be
interrupt-safe, where as digitalRead/digitalWrite/pinMode() are carefully coded to work when used
both in an ISR and the main program.

Klebwax
03.10.2018, 12:00
Eine interessante Stelle ist die Verwendung von Interruptroutinen. Der Compiler rettet beim Aufruf meist mehr Register auf dem Stack als nötig.

Da wäre ich sehr vorsichtig. C ist eine Sprache, die eigentlich für einen Prozessor mit mindestens 16 Bit gedacht ist. Nun können die 16 Bit Funktionen durch Inline-Code nachgebildet werden. Manche werden aber auch durch Code in der Compiler Library erledigt. Das kann sogar bei sehr änlichem Code unterschiedlich gehandhabt werden. Mal kann man im Assemblerlisting sehen, welche Register benutzt werden, mal wird eine eingebaute Funktion aufgerufen, deren Registerbenutzung man nicht kennt. Das gilt ebenso für die Funktionen der C-Library. Und selbst wenn man das genau untersucht und verfiziert hat, kann einem schon eine neue Version des Compilers oder der Libc ins Bier spucken. Wenn man dabei ist, sein eigenes Programm zu debuggen, möchte man eigentlich nicht, daß noch Fehler durch zerstörte Register dazu kommen.

MfG Klebwax

HaWe
03.10.2018, 12:06
nicht zu vergessen, dass nur wenige Funktionen für die Cores in C programmiert sind, während der überwiegende Code auf C++ (!) Libs beruht, die durch Java-Programme und deren erzeugte makefiles von gpp zu executables zusammengesetzt werden.

Mxt
03.10.2018, 12:19
die durch Java-Programme und deren erzeugte makefiles
Kleine Ergänzung: Seit ein paar Jahren (Arduino 1.6.6.) ist der Arduino Builder in Go statt in Java geschrieben.

HaWe
03.10.2018, 12:33
Kleine Ergänzung: Seit ein paar Jahren (Arduino 1.6.6.) ist der Arduino Builder in Go statt in Java geschrieben.

aber Java werkelt doch auch noch mit rum, oder ist das auch ersetzt?

Mxt
03.10.2018, 12:41
In der IDE schon, die ist ja in Processing geschrieben. Im eigentlichen Buildprozess, den man auch auf der Kommandozeile benutzen kann, meines Wissens nach nicht mehr.

Es steht auf der Github Seite ja auch nur


Building from source:

You need a recent version of Go (>=1.8.0).

Moppi
03.10.2018, 14:55
Also jetzt stehe ich gerade mächtig auf dem Schlauch. Ich versuche in Register r16 das High-Byte einer Integer-Variablen zu laden. Mittels "ldi r16, ...". Nichts, was ich versuche will funktionieren. Entweder: Garbage at end of line oder was anders was er meckert. Versuche ich es direkt mit "ldi r16,%0" haut er mir um die Ohren: undefined reference to `r14' - Boahhhh ! :mad:

Muss erst mal Kaffee org.