Mikrocontroller mit Arm Cortex M4F enthalten eine FPU für float. Wenn
ich aber stattdessen double verwende, profitiert die Performance dann
auch von der FPU oder wird sie gar nicht benutzt? Ich compiliere mit gcc
und verwende die newlib-nano Bibliothek, falls das eine Rolle spielt.
Konkret geht es mir aktuell um den STM32F303CC und STM32F303RE.
Stefan ⛄ F. schrieb:> Wenn> ich aber stattdessen double verwende, profitiert die Performance dann> auch von der FPU oder wird sie gar nicht benutzt?
Weder, noch. Die Performance sinkt leicht ab.
Grund: Funktions Parameter werden (nach ABI) in der FPU übergeben -
Laden und Speichern von Double geht nämlich, nur das eigentliche Rechnen
nicht.
Das bedeutet aber auch das man für die eigentliche Berechnung die Daten
wieder aus der FPU zurück in die CPU kopieren muss. Daher kommt der
(IIRC kleine) Performance Verlust.
Danke Jim. Jetzt bin ich allerdings genau so dumm wie vorher. Ich glaube
ich habe meine Frage unklar formuliert. Ich versuche es noch mal:
Laufen double Berechnungen mit FPU schneller als ohne FPU?
Dass double langsamer als float wird ist mir klar. Ich hoffe dabei, dass
die Genauigkeit nicht automatisch auf die von float herunter fällt, oder
doch?
FPU in Mikrocontrollern ist für mich noch totales Neuland.
Stefan ⛄ F. schrieb:> Laufen double Berechnungen mit FPU schneller als ohne FPU?
Doch nur, wenn die FPU auch double kann. Wie soll das denn auch sonst
gehen?
Wenn Du double brauchst, dann nimm double. Die STM32 sind auch per
Emulation recht flott.
m.n. schrieb:> Doch nur, wenn die FPU auch double kann. Wie soll das denn auch sonst> gehen?
Ich weiß es nicht. Ich dachte mir, dass die double Berechnungen
vielleicht iterativ unter Ausnutzung der FPU stattfinden könnten.
Mich interessiert allerdings nicht, was theoretisch machbar wäre,
sondern was der Compiler bzw. die C Bibliothek daraus macht.
Stefan ⛄ F. schrieb:> sondern was der Compiler bzw. die C Bibliothek daraus macht.
Nimm ein Testprogramm und schau dir den generierten Assemblercode an.
Bin mir nicht sicher aber ich meine, dem GCC muss man bei ARM per Option
mitteilen, dass er Code generiert, der die FPU anspricht.
Aber die Aussage bitte mit Vorsicht betrachten, du weißt Erinnerungen ;)
Stefan ⛄ F. schrieb:> Ich dachte mir, dass die double Berechnungen> vielleicht iterativ unter Ausnutzung der FPU stattfinden könnten.
Nein, die (float) FPU ist dafür unbrauchbar.
Mantisse und Exponent werden schön mit uint32_t gerechnet.
Das Programm gibt schön regelmäßig aus:
> f=21916.677734, duration=22 ms
Aber wenn ich die Auskommentierten Zeilen aktiviere bricht es noch vor
der ersten Zeile von main() mit einer Exception ab.
Ich compiliere mit -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard
-mthumb -O1 -g
Wenn ich die Aktivierung der FPU hingegen so mache, stürzt es nicht mehr
ab:
1
voidSystemInit()
2
{
3
// Switch the FPU on
4
SCB->CPACR=0x00F00000;
5
}
Ist das so in Ordnung, oder habe ich noch einen anderen Fehler gemacht?
Stefan ⛄ F. schrieb:> Ich compiliere mit -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard> -mthumb -O1 -g>> Wenn ich die Aktivierung der FPU hingegen so mache, stürzt es nicht mehr> ab:void SystemInit()> {> // Switch the FPU on> SCB->CPACR = 0x00F00000;> }>> Ist das so in Ordnung, oder habe ich noch einen anderen Fehler gemacht?
Das sind ja auch 2 paar Schuhe. dem Compiler sagts du nur, dass du eine
fpu hast und verwenden willst und er baut die op-codes so und bindet die
richtige lib ein. Davon weiss die fpu aber solange nichts, bis du sie im
Controller auch an schaltest. Und wenn nicht knallt's.
Das f=f*1.001 schmeisst der Compiler raus, wenn du das if...
auskommentierst. Deshalb läuft es so auch. Also, alles so wie es sein
soll.
und f=f*1.001; ist eine Operation mit double durch das 1.001. Es wäre
float mit f=f*1.001f;
Wenn man Code aus Arduino oder AVR Libs übernimmt, dann wird da immer
unnötig double verwendet weil der gcc für AVR da bisher in float
gerechnet hat.
Man kann per Holzhammer Methode alle FP Konstanten als default float
übersetzen lassen, ist aber auch unschön.
Johannes S. schrieb:> und f=f*1.001; ist eine Operation mit double durch das 1.001. Es wäre> float mit f=f*1.001f;
Das wollte ich gerade auch schreiben, ist das in C nicht sogar Standard?
> Das f=f*1.001 schmeisst der Compiler raus
Ganz sicher nicht, das erkenne ich an der printf() Ausgabe und dem
Timing.
Ich habe eher das Gefühl, dass die auskommentierten Zeilen einen Zugriff
auf die FPU noch vor Ausführung der main() auslösen. Denn wenn ich den
float-Kram in eine eigene funktion auslagere (und SystemInit() leer
lasse) stürzt es nicht mehr ab:
> und f=f*1.001; ist eine Operation mit double durch das 1.001.> Es wäre float mit f=f*1.001f;
Oha. Ich habe mich schon gewundert warm float nur marginal schneller war
als double. Da seht ihr, warum ich vor meinen Experimenten gefragt
hatte, welche Ergebnisse zu erwarten wären. Was nützt mir eine Serie von
Tests mit falschen Ergebnissen, wenn ich den Fehler nicht bemerke?
Jetzt komme ich auf deutlich plausiblere Messwerte:
1
F303mitFPU
2
f=2555.784180,duration=22ms
3
d=2540.327709,duration=299ms
4
5
F103ohneFPU
6
f=2555.784180,duration=210ms
7
d=2540.327709,duration=325ms
Das war mit der obigen test_float() Funktion und genau das Gleiche
nochmal mit double.
Ich sehe in der FPU einen Geschwindigkeitvorteil von Faktor 10, aber nur
bei float. Der Test bestätigt die Aussage von m.n., dass die FPU bei
double nutzlos ist.
Der Vergleich mit dem F103 ohne FPU zeigt dass dort double nur wenig
langsamer ist, als float.
Für mich bleibt jetzt nur noch diese Frage offen:
Stefan ⛄ F. schrieb:> wenn ich die Auskommentierten Zeilen aktiviere bricht es noch vor> der ersten Zeile von main() mit einer Exception ab.
Stefan ⛄ F. schrieb:> Der Vergleich mit dem F103 ohne FPU zeigt eine für mich einen> überraschend geringen Overhead von double gegenüber float.
naja 55% mehr Rechenzeit würde ich jetzt nicht als gering bezeichnen...
Die Frage ist eh wann man double wirklich braucht, für die meisten
Anwendungen ist float ausreichend, und wenn nicht hat man meistens eh
komplexere Berechnungen und dann kann man entsprechend einen µC DPFPU
benutzen.
Es gibt im übrigen Application Notes von ST zu dem Thema: AN4044
Der Knackpunkt ist hier wohl die vpush Anweisung (Push extension
registers onto the stack). Wenn ich den if-Block auskommentiere entfällt
diese Anweisung.
Interessant, also sichert der GCC hier schon am Anfang der Funktion ein
Float register.
Wenn man also die FPU in der Main funktion verwenden will, muss die FPU
schon davor aktiviert sein. Wahrscheinlich am einfachsten, das direkt im
Startup Code zu machen.
Eventuall könnte auch eine Funktion mit
1
__attribute__((constructor))
2
voidinitFPU(){
3
SCB->CPACR=0x00F00000;
4
}
Funktionieren. Die Funktion sollte der GCC vor Eintritt in die main
schon aufrufen.
Alex D. schrieb:> Wenn man also die FPU in der Main funktion verwenden will, muss die FPU> schon davor aktiviert sein. Wahrscheinlich am einfachsten, das direkt im> Startup Code zu machen.
So habe ich mir das jetzt auch notiert. Ich mache es wie oben gezeigt in
SystemInit().
Auf den M0 hauen die double noch mehr rein weil der gcc da schlechter
optimiert, da entscheidet ein ‚f‘ schon oft darüber ob der Code noch in
den Flash passt oder nicht. Zumindest bei den kleinen mit 16 oder 32 kB.
Stefan ⛄ F. schrieb:> Jetzt komme ich auf deutlich plausiblere Messwerte:> F303 mit FPU> f=2555.784180, duration=22 ms> d=2540.327709, duration=299 ms> F103 ohne FPU> f=2555.784180, duration=210 ms> d=2540.327709, duration=325 ms
Es wäre schön, wenn Du die Laufzeit noch auf einen einzigen Durchlauf
umrechnen würdest, sonst werden eventuell Gerüchte genährt, die
float/double Operationen auf einem STM32 als langsam bezeichnen.
Tatsächlich sind die Laufzeiten ja im µs Bereich.
m.n. schrieb:> Es wäre schön, wenn Du die Laufzeit noch auf einen einzigen Durchlauf> umrechnen würdest
Wenn ich die Wurzel heraus nehme, also nur noch Multiplikationen mache,
komme ich beim F303 mit 72 MHz auf
* 4800 float Multiplikationen pro ms
* 330 double Multiplikationen pro ms
Oder anders gesagt:
* 15 Takte pro float Multiplikation
* 218 Takte pro double Multiplikation
(die Wiederholschleife mit einbezogen)
Mein Ziel war hier aber nicht, einen umfassenden Benchmark zu machen,
sondern herauszufinden, ob die FPU bei double überhaupt etwas bringt.
Durch Vergleich mit dem F103 konnte ich bestätigen, daß dem nicht so
ist.
Stefan ⛄ F. schrieb:> Jetzt komme ich auf deutlich plausiblere Messwerte:
Könntest Du die Messungen nochmal mit uint32_t wiederholen? Dann
natürlich mit einem ganzzahligen Multiplikator, etwa 314U und einem
folgenden Rechtsshift, etwa >>=4.
Dann könnte man gucken, inwieweit sich manuelles Fixpunktgehacke noch
lohnt.
Stefan ⛄ F. schrieb:> vpushAlex D. schrieb:> Interessant, also sichert der GCC hier schon am Anfang der> Funktion ein> Float register.
Interessant ist das im Grunde genommen gar nicht. Denn der Compiler
macht genau das, was er gemäß ABI tun muss, er sichert alle
erforderlichen Register beim Einsprung in die Funktion main().
Daher ist es offensichtlich falsch, CAPCR erst in main() zu
konfigurieren.
Daher entweder:
1. In main kein float benutzen
oder
2. CAPCR vor der main() konfigurieren
Wenn man es 100% korrekt machen möchte, dann ist es sogar (aufgrund des
Pipelinings des Cortex) nötig, mittels DSB und ISB den Zugriff auf CAPCR
zu synchronisieren, da es sonst trotzdem knallen könnte...
900ss D. schrieb:> Nop schrieb:>> Dann könnte man gucken, inwieweit sich manuelles Fixpunktgehacke noch>> lohnt>> Es lohnt sich mit Sicherheit, wenn keine FPU vorhanden ist.
Das sehe ich ganz anders, es sei denn, man baut sich gerne Fehler ins
Programm, ohne irgendeinen Vorteil zu bekommen ;-)
Til S. schrieb:> Was evtl. mehr bringen könnte wäre eine optimierte Float Library:
Der Vergleich der benötigten Codegröße hinkt ein wenig, da angenommen
wird, man müßte immer den ganzen trigonometrischen Kram mit dazu linken.
Die Grundrechenarten sind in der Regel sehr kompakt und - das vermute
ich mal - bei allen Anbietern auch in Assembler geschrieben.
Ferner sollte man auch den Preis im Auge behalten. Eine STM32H7xx MPU
muß garnicht so teuer sein(H730/H750), spart nochmals Code und rechnet
sicherlich eine Größenordnung schneller.
Meine Meinung zu diesem Thema.
m.n. schrieb:> Ferner sollte man auch den Preis im Auge behalten. Eine STM32H7xx MPU> muß garnicht so teuer sein(H730/H750), spart nochmals Code und rechnet> sicherlich eine Größenordnung schneller.> Meine Meinung zu diesem Thema.
Nur dass die STM32H730/750 nur 128KiB Flash haben im Gegensatz zu
Stefans STM32F303RE mit 512KiB Flash.
Könnte also knapp werden bei 75% weniger Flash...
Oder man landet bei wesentlich teureren M7.
Dr. MCU schrieb:> Interessant ist das im Grunde genommen gar nicht. Denn der Compiler> macht genau das, was er gemäß ABI tun muss, er sichert alle> erforderlichen Register beim Einsprung in die Funktion main().
Wie erklärst du dir dann, dass das Programm ohne den if-Block auf das
Sichern der Register verzichtet?:
John Doe schrieb:> Nur dass die STM32H730/750 nur 128KiB Flash haben im Gegensatz zu> Stefans STM32F303RE mit 512KiB Flash.
Wobei ich wohl den Tag nicht mehr erleben werde, an dem die 512kB voll
werden.
Stefan ⛄ F. schrieb:> Wie erklärst du dir dann, dass das Programm ohne den if-Block auf das> Sichern der Register verzichtet?
Ich hab's (Knöppe vor den Augen): Der Compiler hat die ganze for
Schleife weg optimiert. Raffiniert!
m.n. schrieb:>> Oder man landet bei wesentlich teureren M7.>> H730/H750 sind M7 mit "affenschnellen" 550/480 MHz ;-)
evtl. war es ja so gemeint das die M7 mit viel Flash auch gleich
deutlich teurer sind, ein H743 kostet ca. 5€ mehr.
edit:
Und der H743 rechnet auch double in Hardware.
Johannes S. schrieb:> m.n. schrieb:>>> Oder man landet bei wesentlich teureren M7.>>>> H730/H750 sind M7 mit "affenschnellen" 550/480 MHz ;-)>> evtl. war es ja so gemeint das die M7 mit viel Flash auch gleich> deutlich teurer sind, ein H743 kostet ca. 5€ mehr.
Genauso war das gemeint. Die H730/50 sind kein passendes "Upgrade", da
sie viel weniger Flash-Speicher haben.
M7 mit gleichviel Speicher sind halt eben deutlich teurer.
m.n. schrieb:> Das sehe ich ganz anders, es sei denn, man baut sich gerne Fehler ins> Programm, ohne irgendeinen Vorteil zu bekommen ;-)
Ich hatte Integerrechnung im Kopf bei meiner Antwort.
Hmmmm.... ich habe Integer mit Fixpoint verwechselt. Wobei es ja oft
notwendig ist, die Integerwerte zu skalieren, was ja eigentlich ein
verstecktes Fixpoint ist.
900ss D. schrieb:> Wobei es ja oft> notwendig ist, die Integerwerte zu skalieren, was ja eigentlich ein> verstecktes Fixpoint ist.
Ja gut. Aber diese Fixpointgeschichte ist doch aus einer Zeit, als die
CPUs noch mit 1 MHz getaktet wurden. Manche Leute meinen, man müsse es
bis in alle Zeiten so fortsetzen und haben Angst vor float und
Interrupts, die mehr Anweisungen haben als PUSH, POP und RETURN.
Bei den oben gezeigten Ausführungszeiten im µs-Bereich selbst für
double-Berechnungen ist es doch nicht mehr notwendig, solche Tricks
anzuwenden.
Lass den Compiler doch optimieren und den Controller das rechnen: man
schreibt die Formeln so, wie man es braucht.
John Doe schrieb:> M7 mit gleichviel Speicher sind halt eben deutlich teurer.
Und was kosten F303 mit >= 512 KB RAM?
m.n. schrieb:> Lass den Compiler doch optimieren und den Controller das rechnen: man> schreibt die Formeln so, wie man es braucht.
Ich stimme dir zu aber Integerrechungen sind einfach schneller als Float
wenn man keine FPU hat. Und darum ging es. Ob es sinnvoll ist, auf Float
zu verzichten muss ja jeder selber entscheiden. Bei 8-Bit MCUs würde ich
da schon eher hinsehen ;)
If you use the GCC option "-fshort-double" (Use the same size for double
as for float) then a single precision FPU would be used for "double"
operations.
Wenn man nur float nutzen möchte, dann lohnt es sich, den Code mal mit
-Wdouble-promotion zu kompilieren.
So sieht man schön, wo man den f suffix bei Konstanten vergessen hat
oder ausversehen double Routinen aufruft.
HackerJ schrieb:> If you use the GCC option "-fshort-double" (Use the same size for double> as for float) then a single precision FPU would be used for "double"> operations.
Ja aber dann auch nur mit der Genauigkeit von float.
Stefan ⛄ F. schrieb:> HackerJ schrieb:>> If you use the GCC option "-fshort-double" (Use the same size for double>> as for float) then a single precision FPU would be used for "double">> operations.>> Ja aber dann auch nur mit der Genauigkeit von float.
Schön wäre es. Das Flag gibt es leider nicht mehr, jedenfalls nicht für
Cortex-M. Vielleicht noch für einzelne Architekturen wie Mips (als
-mshort-double?).
Beitrag "GCC Compiler Flags"