Hallo zusammen,
da ich immer mal einen Blick auf den vom GCC erzeugten Assembler-Code
werfe (man lernt ja nie aus), ist mir heute folgendes aufgefallen:
simpelste Instruktionen zur UART-Steuerung werden verschieden codiert:
(das Z-Register wird nachher nicht weiter verwendet, unmittelbar danach
folgt ein ret)
ich würde gerne verstehen warum im zweiten Fall nicht auch mit lds/sts
gearbeitet wird?
Danke, Michi
Michael Reinelt schrieb:> ich würde gerne verstehen warum im zweiten Fall nicht auch mit lds/sts> gearbeitet wird?
Erste Hinweise geben dir die Dumps, welche mit -fdump-rtl-all
-fdump-tree-all erzeugt werden können.
Johann L. schrieb:> Erste Hinweise geben dir die Dumps, welche mit -fdump-rtl-all> -fdump-tree-all erzeugt werden können.
Ähhhh.... ich fürchte das sind eine Spur zu viele Hinweise :-) Der
erzeugt mir jetzt ja gefühlte 2000 Dumps :-(
Hat jemand eine idee wo ungefähr ich da reinschauen sollte?
Michael Reinelt schrieb:> Johann L. schrieb:>>> Erste Hinweise geben dir die Dumps, welche mit -fdump-rtl-all>> -fdump-tree-all erzeugt werden können.>> Ähhhh.... ich fürchte das sind eine Spur zu viele Hinweise :-) Der> erzeugt mir jetzt ja gefühlte 2000 Dumps :-(
Schamlose Übertreibnug, da sind maximal 150-200 Dumps, mehr nicht.
>> Hat jemand eine idee wo ungefähr ich da reinschauen sollte?
Schau dir an, wo die Ersetzung im erwarteten Fall (nicht) stattfindet
und vergleiche das mit dem unliebsamen Fall.
Wenn der Pass gefunden ist, in dem die Ersetzung (nicht) stattfindet,
dann
1) Ist das der Pass, in dem die Ersetzung wirklich stattfinden soll?
IIRC wird teilweise in insn combine ersetzt, was aber mir aber nicht
zielführend erscheint.
2) Wenn der Pass gefunden ist, gehst du in die Quellen und suchst dort
den Übeltäter und erstellst einen Fix / ein Patch nebst Testfall :-)
Johann L. schrieb:> Michael Reinelt schrieb:>> Johann L. schrieb:>>>>> Erste Hinweise geben dir die Dumps, welche mit -fdump-rtl-all>>> -fdump-tree-all erzeugt werden können.>>>> Ähhhh.... ich fürchte das sind eine Spur zu viele Hinweise :-) Der>> erzeugt mir jetzt ja gefühlte 2000 Dumps :-(>> Schamlose Übertreibnug, da sind maximal 150-200 Dumps, mehr nicht.
ich sagte ja "gefühlt" :-)
>> Hat jemand eine idee wo ungefähr ich da reinschauen sollte?>> Schau dir an, wo die Ersetzung im erwarteten Fall (nicht) stattfindet> und vergleiche das mit dem unliebsamen Fall.>> Wenn der Pass gefunden ist, in dem die Ersetzung (nicht) stattfindet,> dann>> 1) Ist das der Pass, in dem die Ersetzung wirklich stattfinden soll?> IIRC wird teilweise in insn combine ersetzt, was aber mir aber nicht> zielführend erscheint.>> 2) Wenn der Pass gefunden ist, gehst du in die Quellen und suchst dort> den Übeltäter und erstellst einen Fix / ein Patch nebst Testfall :-)
na du machst mir Spaß :-)
Aber da ich an chronischer Neugierde im Endstadium leide, werd ich mir
das antun... aber erst morgen.
Ich kann zwar RTL genau überhaupt nicht, aber ich vermute das steht für
"Register tranfer language", was so ein Zwischenformat des GCC ist.
Es gibt auch so einen Modus "-dP" oder so, wo er in den Assembler-Code
die RTL-Statements als Kommentare reinzieht. Da hab ich heut mal kurz
drübergeschaut (hab leider das Ergebnis grad nicht bei der Hand), aber
die RTL-Statements sahen für mich zumindest ähnlich aus. Daher meine
Frage: kann es sein dass der Unterschied gar nicht in der RTL
aufscheint, sondern erst später, wo RTL in den tatsächlichen Code
übersetzt wird? (RTL ist ja ein Stück weit "prozessor-neutral"?)
Was ich auch noch prüfen möchte: Der Code mit Z-Pointer sieht zwar
komplexer aus, aber diese Operationen laufen ja schneller als ein "lds".
Vielleicht ist es sowohl von code-size als auch von cycles Jacke wie
Hose, welcher Code erzeugt wird?
Vielleicht hängt es auch damit zusammen, dass eine Variante eine
"normale" Funktion ist, während die andere aus einer ISR kommt?
Ich muss morgen auf jeden Fall einen echten, vereinfachten Testcase
erstellen....
anyway: vielen Dank für deine Unterstützung!
A. K. schrieb:> Michael Reinelt schrieb:>> Vielleicht ist es sowohl von code-size als auch von cycles Jacke wie>> Hose, welcher Code erzeugt wird?>> Und genau so ist es.
hast du nachgezählt?
Michael Reinelt schrieb:> Johann L. schrieb:>> Michael Reinelt schrieb:>>> Johann L. schrieb:>>> Hat jemand eine idee wo ungefähr ich da reinschauen sollte?>>>> Schau dir an, wo die Ersetzung im erwarteten Fall (nicht) stattfindet>> und vergleiche das mit dem unliebsamen Fall.>>>> Wenn der Pass gefunden ist, in dem die Ersetzung (nicht) stattfindet,>> dann>>>> 1) Ist das der Pass, in dem die Ersetzung wirklich stattfinden soll?>> IIRC wird teilweise in insn combine ersetzt, was aber mir aber nicht>> zielführend erscheint.>>>> 2) Wenn der Pass gefunden ist, gehst du in die Quellen und suchst dort>> den Übeltäter und erstellst einen Fix / ein Patch nebst Testfall :-)>> na du machst mir Spaß :-)
Naja, weg Fragt muß auch mit einer Antwort rechnen :-P
> Ich kann zwar RTL genau überhaupt nicht, aber ich vermute das steht für> "Register tranfer language", was so ein Zwischenformat des GCC ist.
Jepp
> Es gibt auch so einen Modus "-dP" oder so, wo er in den Assembler-Code> die RTL-Statements als Kommentare reinzieht. Da hab ich heut mal kurz> drübergeschaut (hab leider das Ergebnis grad nicht bei der Hand), aber> die RTL-Statements sahen für mich zumindest ähnlich aus.
Einmal müsste es eine indirekte Adressierung sein wie
1
(set (reg:QI Rn)
2
(mem:QI (reg:HI r30)))
was ein "LD Rn, Z" bedeutet. Im Fall einer direkten Adressierung mit zur
Compilezeit bekannter Adresse sowas wie
1
(set (reg:QI Rn)
2
(mem:QI (const_int XYZ)))
Was ein "LDS Rn, XYZ" bedeutet.
Für die Maschinenmodi siehe z.B.
http://gcc.gnu.org/onlinedocs/gccint/Machine-Modes.html#Machine-Modeshttp://gcc.gnu.org/wiki/avr-gcc#Exceptions_to_the_Calling_Convention> Daher meine> Frage: kann es sein dass der Unterschied gar nicht in der RTL> aufscheint, sondern erst später, wo RTL in den tatsächlichen Code> übersetzt wird? (RTL ist ja ein Stück weit "prozessor-neutral"?)
Nein. Idee und Ziel von RTL ist, eine arithmetische / algebraische
Beschreibung einer Assembler-Instruktion darzustellen. Im Laufe der
RTL-Passes werden die insns immer weiter in Richtung "echter"
Instruktionen umgewandelt und im lezten Pass lediglich in Text (s-File)
umgewandelt. Das -dP Dump enthält die letzten insns als Kommentar und
nachfolgend die entsprechende(n) Assembler-Instruktione(n).
Die Syntax und Semantik von RTL ist Target-übergreifend. Target-abhängig
ist hingegen, welche RTL-Konstrukte erlaubt sind. Was in (mem:QI
(const_int 0x1234)) bedeutet ist z.B. Target-unabhängig, allerdings ist
so ein Speicherzugriff nur dann gültig, wenn das Target die
entsprechende Adressierungsart auch unterstützt.
> Was ich auch noch prüfen möchte: Der Code mit Z-Pointer sieht zwar> komplexer aus, aber diese Operationen laufen ja schneller als ein "lds".> Vielleicht ist es sowohl von code-size als auch von cycles Jacke wie> Hose, welcher Code erzeugt wird?
Siehe unten.
> Vielleicht hängt es auch damit zusammen, dass eine Variante eine> "normale" Funktion ist, während die andere aus einer ISR kommt?
Nö, das spielt keine Rolle. Eher von bedeutung ist volatile.
A. K. schrieb:> Michael Reinelt schrieb:>> Vielleicht ist es sowohl von code-size als auch von cycles Jacke wie>> Hose, welcher Code erzeugt wird?>> Und genau so ist es.
Ich habe aber auch schon Fälle gesehen, wo es ungünstiger ist, und von
der Registerlast her ist es auf jeden Fall schlecht. Dass es hier nicht
mieser ist als direkte Adressierung ist eher Zufall aber kein Design.
Und hier gibts tatsächlich einen unterschied:
einmal ist es
set (reg:QI 24 r24) (mem/v:QI (reg/f:HI 30 r30 [44]))
und das zweitmal
(set (reg:QI 24 r24) (mem/v:QI (const_int 193 [0xc1]))
jetzt weiss ich aber nicht mehr weiter :-(
Johann, magst du mir nochmal helfen bitte?
A. K. schrieb:> LDS/STS 2 Takte 2 Worte, der Rest 1 Takt 1 Wort. Zähl selbst.
Ich hab nachgezählt:
LDS 2W 2Cycle
ORI 1W 1C
STS 2W 2C
---------
5W 5C
LDI r30 1W 1C
LDI r31 1W 1C
LD,Z 1W 1C
ORI 1W 1C
ST,Z 1W 2C
-------------
5W 6C
der "komplizierte" Code ist zwar gleich lang (5 Wörter) braucht aber
einen takt länger. oder hab ich mich verzählt?
ich hab mir tatsächlich das RTL angetan :-) und zumindest die Stelle
gefunden wo der Unterschied auftritt:
nach pass 161 (forward propagation) sieht der Code noch gleich aus,
interessanterweise beide male noch mit "indirekter" Adressierung:
was ja eigentlich "besser" und "hübscher" ist.
ich verstehe zwar nicht was das mit "constant propagation" zu tun hat,
aber noch viel weniger verstehe ich warum diese Änderung/Verbesserung im
"einfacheren" Fall ohne if() nicht durchgeführt wird...
Michael Reinelt schrieb:> der "komplizierte" Code ist zwar gleich lang (5 Wörter) braucht aber> einen takt länger. oder hab ich mich verzählt?
Braucht LD Rd,Z nicht 2 Zyklen?
Yalu X. schrieb:> Michael Reinelt schrieb:>> der "komplizierte" Code ist zwar gleich lang (5 Wörter) braucht aber>> einen takt länger. oder hab ich mich verzählt?>> Braucht LD Rd,Z nicht 2 Zyklen?
Wenn ich das Datenblatt richtig lese, braucht die "einfache" Variante
ohne Inkrement/Dekrement des Z-Pointers nur einen Takt.
Michael Reinelt schrieb:> Wenn ich das Datenblatt richtig lese, braucht die "einfache" Variante> ohne Inkrement/Dekrement des Z-Pointers nur einen Takt.
Von welchem COntroller reden wir?
Beim ATmega48 sind es 2 Zyklen, bei den neuen, halbierten AVRs
(ATtiny4/5/9/10) kann es evtl. anders aussehen.
Yalu X. schrieb:> Michael Reinelt schrieb:>> Wenn ich das Datenblatt richtig lese, braucht die "einfache" Variante>> ohne Inkrement/Dekrement des Z-Pointers nur einen Takt.>> Von welchem COntroller reden wir?>> Beim ATmega48 sind es 2 Zyklen, bei den neuen, halbierten AVRs> (ATtiny4/5/9/10) kann es evtl. anders aussehen.
ATmega328P
Wo schaust du nach? ich hier: http://www.atmel.com/Images/doc0856.pdf
Seite 93
Wobei beim XMEGA dabeisteht dass es bei Zugriff auf internes SRAM ein
zyklus mehr ist, aber der 328 ist doch kein XMEGA, oder?
Und dann gibts da noch so Fußnoten...
Aber ich lese das so, dass das 1 zyklus braucht.
m.n. schrieb:> Schon lustig. In dem Datenblatt von 9/2001 stehen da konstant 2 Zyklen.
Steht auch in älteren Versionen der instruction set summary. Das
mit dem einen Zyklus trifft wirklich nur auf die tiny-Architektur
(ATtiny10 & Co.) zu bzw. auf die IO-Register vom Xmega. Die Fußnoten
klingen auch verdammt nach „tiny“. (Nur dort ist der Flash in den
normalen Adressbereich gemapt.)
Da hat man wohl den Abschnitt zuerst für den Xmega und dann für den
TinyTiny aufgebohrt.
Nach der Tabelle auf Seite 13 im aktuellen Instruction-Set-Manual sind
es ebenfalls 2 Zyklen für gewöhnliche AVRs und 1 Zyklus für Reduced-
Core-TinyAVRs. Vielleicht wäre so langsam mal wieder ein neues Release
dieses Dokuments fällig ;-)
Ok, jetzt hab ich das "instruction Set Summary" im Datenblatt des 328P
auch (zum erstenmal) gesehen, und da stehen auch 2 zyklen. Also werdens
wohl wirklich zwei sein. Was den komplizierteren Code also nochmal
langsamer macht...
Michael Reinelt schrieb:> nach pass 161 (forward propagation) sieht der Code noch gleich aus,> interessanterweise beide male noch mit "indirekter" Adressierung:> [...]>> nach pass 162 (constant propagation) wird die Variante mit dem "if"> plötzlich zu>
> was ja eigentlich "besser" und "hübscher" ist.>> ich verstehe zwar nicht was das mit "constant propagation" zu tun hat,
Propagiert wird die (compiletime) konstante Adresse 193, die nach
Pseudo-Register r44 geladen wird. Pseudo r44 wird später ins Z-Register
r30 allokiert. (Je nach Testfall unterscheidet sich die Anzahl der
benötigten Pseudos und damit auch deren Numerierung.)
> aber noch viel weniger verstehe ich warum diese Änderung/Verbesserung im> "einfacheren" Fall ohne if() nicht durchgeführt wird...
Hier ist dann Debuggen des Compilers angesagt und nachvollziehen der
Quelle. Im einfachsten Falle sind es fehlende / fehlerhafte
Kostenbeschreibungen im avr-Backend.
IIRC werden Speicherzugriffe in neueren GCC-Versionen zunächst indirekt
umgesetzt und erst in einem späteren Pass wieder in direkte Zugriffe
gewandelt falls dies Vorteilhaft erscheint. Dies ermöglicht z.B. bessere
Optimierung von Adressen die in Schleifen verwendet werden.
Pass "cprop" ist in gcc/cprop.c implementiert:
http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/cprop.c?content-type=text%2Fplain&view=co
Vielleicht liefert -fdump-rtl-cprop-details o.ä. noch detailiertere
Infos. Kosten können im avr-gcc mit -mlog=rtx_costs ausgegeben werden.
Michael Reinelt schrieb:> Was den komplizierteren Code also nochmal> langsamer macht...
Freu Dich, dass Du den "komplizierten Code" nicht komplett in Assembler
schreiben mußt.
Um einzelne Taktzyklen solltest Du Dich erst dann kümmern, wenn dieser
Code >100000 Mal/s aufgerufen wird und Dein Controller schon mit 20MHz
läuft.
Alles andere ist vergeudete Zeit.
m.n. schrieb:> Michael Reinelt schrieb:>> Was den komplizierteren Code also nochmal>> langsamer macht...>> Freu Dich, dass Du den "komplizierten Code" nicht komplett in Assembler> schreiben mußt.> Um einzelne Taktzyklen solltest Du Dich erst dann kümmern, wenn dieser> Code >100000 Mal/s aufgerufen wird und Dein Controller schon mit 20MHz> läuft.> Alles andere ist vergeudete Zeit.
Gut dass nicht alle so denken. Open Source wie GCC lebt nun mal von
engagierten Mitgliedern der Community, die das nicht als "vergeudete
Zeit" ansehen, solche Eigenartigkeiten untersuchen und als Bug Report
eintüten.
Michael Reinelt schrieb:> und als Bug Report eintüten.
Gartuliere, da hast Du ja einen richtig schweren Fehler gefunden.
Ich habe mit gerade einen Codeschnipsel in meinem Compilat angesehen. In
einer ISR erfolgt:
PUSH R1
EOR R1,R1
.
.
POP R1
Zwischendurch wird R1 garnicht gebraucht. Ich könnte jetzt die
verschwendeten Taktzyklen zählen, um zu dokumentieren welch schweren Bug
ich gefunden habe.
Und, mache ich das? NEIN.
Ein Bug Report wäre da auch sinnlos. Der Compiler setzt ohne Kontrolle
oder Ressourcenmanagement voraus, dass in R1 stets 0 drinsteht. Also
muss der Anfang einer ISR diesen Zustand herstellen.
Und ja, eine verkehrt laufende Optimierung gehört wirklich nach
Bugzilla. Dazu ist dieses Werkzeug ebenfalls da, auch wenns kein
dramatischer Fall ist. Ob sich wer drum kümmern wird ist eine andere
Sache, hohe Priorität hat das nicht.
Der Fall hat evtl. etwas bessere Chancen auf Beachtung, wenn man eine
Vorversion findet, die es besser machte, und darauf explizit hinweist
(regression).
A. K. schrieb:> hohe Priorität hat das nicht.
Was man meiner Meinung nach mit hoher Priorität angehen könnte, wären
reservierte Register, die man in eigenen Ass.-Routinen sehr effizient
verwenden kann.
IAR bietet das für R4-R15, was ISRs sehr schnell macht, weil im besten
Fall kein PUSH/POP notwendig ist.
Ich sagte "könnte", denn ich kann das nicht und fordere auch nicht, dass
ein Anderer dies für mich macht.
m.n. schrieb:> Was man meiner Meinung nach mit hoher Priorität angehen könnte, wären> reservierte Register, die man in eigenen Ass.-Routinen sehr effizient> verwenden kann.
Gibt's doch:
1
uint8_ti__asm__("r2");
Musst du halt konsistent in deinem gesamten Projekt durchziehen, und
du musst dich bei den benutzten Bibliotheksroutinen vergewissern, dass
diese derartige Register nicht benutzen.
Es gibt aber keinen Grund, nun allen Nutzern diese Register zu
verbieten, nur weil hin und wieder mal einzelne sowas brauchen.
Daher halt auch die Forderung an denjenigen, der sowas macht, dass
er sich selbst um die Bibliothek kümmern möge, denn die
default-Bibliothek stellt sich das nicht zur Aufgabe. (Man kann sich
aber natürlich auch eine eigene avr-libc compilieren, in der man
konsistent obiges Statement mit einbringt und auf diese Weise das
entsprechende Register im Compiler „reserviert“.
Jörg Wunsch schrieb:> Man kann sich> aber natürlich auch eine eigene avr-libc compilieren, in der man> konsistent obiges Statement mit einbringt und auf diese Weise das> entsprechende Register im Compiler „reserviert“.
Muss man das (obiges Statement einbringen) überhaupt?
Sollte es nicht reichen, beim Kompilieren (der Lib) einfach -ffixed-2 zu
benutzen?
Stefan Ernst schrieb:> Sollte es nicht reichen, beim Kompilieren (der Lib) einfach -ffixed-2 zu> benutzen?
Könnte sein. Ich habe diese Option vorhin in der Doku gesucht, aber
nicht finden können.
m.n. schrieb:> IAR bietet das für R4-R15, was ISRs sehr schnell macht, weil im besten> Fall kein PUSH/POP notwendig ist.
Nur hat IAR ziemlich sicher keine 64-Bit Integers. Denn wenn im
verbleibenden Registersatz keine 2 Operanden Platz finden, dann wirds
schwierig.
A. K. schrieb:> Nur hat IAR ziemlich sicher keine 64-Bit Integers.
Doch, signed und unsigned. Und selbst, wenn man sich alle freien
Register reserviert, geht noch 64-bit double.
Die Variablen werden wohl auf einem separaten CSTACK (Y) abgelegt,
wodurch die Routinen auch noch unterbrechbar werden. Die LDD und STD
Befehle sind dafür gut geeignet. Die Rücksprungadressen liegen auf dem
RSTACK, den der AVR von Hause aus für CALL und RET bietet.
Ohne Frage hat das auch alles seinen Preis, weshalb ich Kritik am GCC
wegen eines Taktzyklus etwas übertrieben finde.
m.n. schrieb:> weshalb ich Kritik am GCC wegen eines Taktzyklus etwas übertrieben> finde.
Darum geht's doch nicht, sondern einfach nur darum, dass es nicht
wirklich nachvollziehbar ist, warum der Compiler es mal so, mal so
entscheidet.