Forum: Mikrocontroller und Digitale Elektronik AVR: C-Code Optimierung mit Inline Assembler


von Thomas W. (Gast)


Lesenswert?

Hallo, liebe Assembler-Freunde!

Ich soll im Rahmen eines Projektes versuchen, C-Code mittels 
Inline-Assembler Code optimieren. Da vor allem Assembler neu für mich 
ist (habe bereits einige Tutorials durchgearbeitet), habe ich jedoch 
keine Ahnung, wo ich ansetzen soll, um den Code performanter zu machen.

Das Board, auf dem das ganze passieren soll, ist ein ArduinoMega2560, 
der C-Code ist ein Cypher. Interessant wäre deshalb vor allem die 
"Round-Function", also die Funktion, die oft auf den Text angewandt 
wird, um ihn zu verschlüsseln, da diese bei einem 
Verschlüsselungsvorgang mehrere hunderte Male angewandt wird und so 
bereits kleine Performanzverbesserungen einen großen Ausschlag geben.

In dieser Funktion geht es hauptsächlich um viele Bitshifts/Rotationen 
und XOR/OR-Operationen. Die Worte, die geshiftet und miteinander geXORt 
werden, sind 16 Bit lang, belegen also jeweils zwei Register.

Meine Frage wäre also, ob ihr mir Tipps geben könntet, wo ich ansetzen 
kann, oder ob es sowieso keinen Sinn macht zu versuchen, besser als der 
Compiler zu sein.

Ich habe bereits gelesen, dass vor allem bei Multiplikationen 
Performanzgewinne gegenüber C möglich sind (z. B. bei 
http://stackoverflow.com/a/577856), jedoch finden in dem Cypher keine 
Multiplikationen statt.

Eine weitere Frage wäre noch, ob es eine Möglichkeit gibt, wie ich an 
den kompilierten avr-gcc Assembler-Code herankommen kann. Bisher habe 
ich nur die Seite http://gcc.godbolt.org/ genutzt, die jedoch nicht die 
aktuelle Version des avr-gcc anbietet.

Vielen Dank im Voraus,
Thomas

: Verschoben durch Moderator
von Borislav B. (boris_b)


Lesenswert?

Falsches Forum...

von Sebastian V. (sebi_s)


Lesenswert?

Wenn du Atmel Studio benutzt, dann sollte bereits eine *.lss Datei mit 
dem generierten Assembler Code erstellt worden sein. Wenn du avr-gcc 
direkt benutzt dann:
avr-objdump -h -S projekt.elf > projekt.lss

Ein guter Anfang ist auf jeden Fall sich den generierten Assembler Code 
anzugucken und dann zu überlegen ob einem Stellen für Verbesserungen 
einfallen.

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


Lesenswert?

Thomas W. schrieb:
> Meine Frage wäre also, ob ihr mir Tipps geben könntet, wo ich ansetzen
> kann

Miss die Ausführungszeiten der einzelnen Teile.

Am besten geht das, wenn man noch ein paar freie IO-Pins hat, mit
denen man „wackeln“ kann, wenn man bestimmte Codeteile betritt oder
verlässt.  Am allerbesten natürlich, wenn man das dann auf einem
Logikanalysator aufzeichnen kann.

Wenn du erstmal weißt, ob deine Runden-Funktion wirklich der
Flaschenhals ist, dann kannst du überlegen, ob sich dort durch
manuelle Optimierung wirklich nennenswert was rausholen lässt.

Boris P. schrieb:
> Falsches Forum...

Verschoben.

von Peter D. (peda)


Lesenswert?

Thomas W. schrieb:
> Ich soll im Rahmen eines Projektes versuchen, C-Code mittels
> Inline-Assembler Code optimieren.

Das ist nur was für eingefleischte Masochisten.
Wenn es unbedingt Assembler sein muß, dann als separates Quellfile 
(*.S).

Aber solange Du nicht der absolute Assembler-Geek bist, sehe ich da 
wenig bis kaum Erfolgschancen.

Welcher Hirni vergibt bloß solche Aufgaben. Hast Du ihm was getan, daß 
er so böse auf Dich ist.

von Karl H. (kbuchegg)


Lesenswert?

Peter Dannegger schrieb:

> Aber solange Du nicht der absolute Assembler-Geek bist, sehe ich da
> wenig bis kaum Erfolgschancen.

Wo ich viel mehr Erfolgschancen sehe, das ist bei der konsequente 
Durchsicht des C Codes. Oftmals ist es so, dass diejenigen, die nach 
Assembler-Optimierung rufen, haarsträubende Probleme im C Code nicht 
sehen. Das beginnt bei der Wahl der falschen Datentypen und geht bis zu 
schlechten Algorithmen bzw. schlechter Umsetzung. Um mal die Phrase "Dem 
Compiler Prügel zwischen die Beine zu werfen" nicht zu verwenden.

Grossartige Performance Gewinne sind durch Assemblerlösungen eigentlich 
selten bis kaum zu erzielen. Dazu sind die Compiler schon zu gut. 
Bitrotation in einem Byte ist eine der Schwachpunkte in C, weil es dafür 
keine High-Level Entsprechung in C gibt, d.h. diese Absicht muss ein 
Optimizer erkennen können. Aber ansonsten? Ob man 2 Werte in C 
miteinander ver-xodert, oder ob man dieselbe Operation in Assembler 
hinschreibt kommt aufs selbe raus.

: Bearbeitet durch User
von Random .. (thorstendb) Benutzerseite


Lesenswert?

Bevor man zu inline asm greift (und der C Code selbst bereits optimiert 
ist), sollte man Compiler Intrinsics in Betracht ziehen.

Beispiel für armcc und Cortex-M3:
16Bit signed BE auf 32Bit signed LE (ansonsten per SHIFT, OR & MASK 
sowie sign extension):
leVal = _REVSH(beVal);

Eine gute Methode ist auch, verschiedene Ansätze in C mit dem compilat 
in ASM zu vergleichen, um zu schauen, was der compiler draus macht. Der 
Gewinn wird ggf. sogar grösser als durch pure ASM Fummelei.

Schlussendlich sollte man Teile seines Programms ausmessen. Dies kann im 
einfachsten Fall per Pin Toggling (über eine Code Section) geschehen, 
oder bei grösseren µCs z.B. durch ein Virtuelles Oszilloskop in der IDE 
mit einem schnellen target Debugger.

von Peter D. (peda)


Lesenswert?

Random ... schrieb:
> Eine gute Methode ist auch, verschiedene Ansätze in C mit dem compilat
> in ASM zu vergleichen, um zu schauen, was der compiler draus macht.

Ja, die Optimierung auf C-Ebene ist deutlich einfacher.
Es reicht dazu, im Assembler-Listing zu zählen, wieviel Instruktionen 
ein C-Ausdruck erzeugt.

von Steffen R. (steffen_rose)


Lesenswert?

Peter Dannegger schrieb:
> Es reicht dazu, im Assembler-Listing zu zählen, wieviel Instruktionen
> ein C-Ausdruck erzeugt.

Da der Optimierer umstrukturiert, kann man selten einzelne ASM Befehle 
konkret einer C-Anweisung zuordnen.

von Noch einer (Gast)


Lesenswert?

> mehrere hunderte Male angewandt

> Da der Optimierer umstrukturiert

Wenn der Compiler da mehrere hundert Funktionsaufrufe durch Inline 
expansion optimiert, kann deine Assembler-optimierte Funktion insgesammt 
zu langsameren Code führen. Wenn der Compiler den Code nicht selbst 
erzeugt hat, optimiert er auch nicht.

von Peter D. (peda)


Lesenswert?

Steffen Rose schrieb:
> Da der Optimierer umstrukturiert, kann man selten einzelne ASM Befehle
> konkret einer C-Anweisung zuordnen.

Wenn man das Inlining abschaltet, geht das recht gut. Zumindest beim 
WINAVR2010. Die Quelltextkommentare sind meistens an der richtigen 
Stelle oder in der Nähe.
Mit Inlining muß man eben an der Aufrufstelle suchen.

von Steffen R. (steffen_rose)


Lesenswert?

Auch der WinAVR2010 wird bei eingeschaltetem Optimierer 
Registerzuweisungen nicht mehrfach ausführen u.a.

Da kann es dann vorkommen, dass manche C-Anweisung als wenig aufwändig 
angenommen wird. Löscht man nun die davorliegende "aufwändige" 
C-Anweisung, verschiebt sich das Befüllen der Register auf die nun nicht 
mehr einfache Anweisung.

Bei -O0 kann man noch gut nachkommen, aber bei -Os muss man schon 
genauer kucken, welche an anderer Stelle liegenden ASM Anweisung noch 
dazugehören.

Wie sehr WinAVR umstrukturiert, weiß ich nicht. Da wirst Du sicher recht 
haben.

von Coder (Gast)


Lesenswert?

Mal' die wichtigen bzw. zeitkritischen Stellen mit Profiling 
identifizieren und betrachten. Und oft sind die Compiler-Bauer die 
besseren Optimierer...

von Noch einer (Gast)


Lesenswert?

Mit Lötkolben sind wir Hobby-Frickler besser als die Compiler-Bauer. Wir 
konnen die Funktion in ein FPGA brennen - die Compiler-Bauer können das 
nicht :-)

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


Lesenswert?

Noch einer schrieb:
> Wir konnen die Funktion in ein FPGA brennen

Mit dem Lötkolben?  Das arme FPGA. :)

von Ulrich H. (lurchi)


Lesenswert?

Es hängt sehr von der Aufgabe ab, ob man von Hand deutlich besser 
optimieren kann als der Compiler. Dazu sollte man aber schon etwas Übung 
in ASM haben. Als Anfänger tut man sich da oft recht schwer.

Gute Chancen hat man mit ASM deutlich schneller zu werden, wenn man in 
ASM kleinere Datentypen nutzen kann, etwa 20 Bit Zahlen oder gemischte 
Operationen wie 16 Bit mal 8 Bit Zahl, die die meisten C-Compiler nicht 
unterstützen. Auch über die direkte Nutzung des Carry flags geht es 
manchmal deutlich schneller als in C, weil man sich ggf. eine IF Else 
sparen kann. Manchmal findet aber auch GCC schon das Optimum oder ist 
dicht dran.

Zum optimieren gibt es 2 Stufen: einmal vom ASM Code ausgehen den der 
Compiler erzeugt hat, und dann den zu Optimieren. Alternativ löst man 
das Problem gleich in ASM, ohne Code vom Compiler als Vorbild - das gibt 
ggf. ganz andere Ansätze wo z.B. IF ..ELSE Unterscheidungen wegfallen 
und stattdessen Flag Bits direkt genutzt werden. Auch Tabellen sind ggf. 
Effektiv.

Wie lange ein Stück Code wirklich braucht, kann man ganz gut im 
Simulator testen: einfach vor und hinter den interessanten Teil 
Breakpoints setzen. Das ist einfacher als von Hand Zyklen zählen.

von Thomas W. (Gast)


Lesenswert?

Vielen Dank schon mal an alle für die vielen, schnellen Antworten! Hat 
mir auf jeden Fall neue Ansätze gegeben.

Ist echt super, dass sich hier so schnell um Probleme gekümmert wird, 
weiter so!

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.