Nutzt gcc/newlib-nano die FPU von Cortex M4F bei double?

Gast #6620004
Lesenswert?

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.
#6620014
Lesenswert?

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.
Gast #6620019
Lesenswert?

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.
Gast #6620082
Lesenswert?

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.
Gast #6620200
Lesenswert?

Dankeschön. Ich mache gerade erste Experimente und bin auf ein ganz 
komische Problem gestoßen:
1
int main()
2
{
3
    init_clock();
4
    init_io();
5

6
    // Initialize the timer for 1 ms intervals
7
    SysTick_Config(SystemCoreClock/1000);
8

9
    // Switch the FPU on
10
    SCB->CPACR = 0x00F00000;
11

12
    printf("Start\n");
13
    delay_ms(100);
14

15
    while (1)
16
    {
17
        // LED an
18
        WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BS_5);
19

20
        uint32_t start=systick_count;
21

22
        float f=1.0;
23
        for (int i=0; i<10000; i++)
24
        {
25
            f=f*1.001;
26

27
            //if (f>100000.0)
28
            //{
29
            //    f=1.0;
30
            //}
31
        }
32

33
        // LED aus
34
        WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BR_5);
35

36
        printf("f=%f, duration=%ld ms\n",f, systick_count-start);
37
        delay_ms(500);
38
    }
39
}

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
void SystemInit()
2
{
3
    // Switch the FPU on
4
    SCB->CPACR = 0x00F00000;
5
}

Ist das so in Ordnung, oder habe ich noch einen anderen Fehler gemacht?
Angehängte Dateien:
Gast #6620208
Lesenswert?

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.
Gast #6620212
Lesenswert?

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.
Gast #6620234
Lesenswert?

> 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:
1
void test_float()
2
{
3
    uint32_t start=systick_count;
4
    float f=1.0f;
5
    for (int i=0; i<100000; i++)
6
    {
7
        f=f*1.001f;
8
        if (f>10000.0f)
9
        {
10
            f=sqrt(f);
11
        }
12
    }
13
    printf("f=%f, duration=%ld ms\n",f, systick_count-start);
14
}
15

16
int main()
17
{
18
    init_clock();
19
    init_io();
20

21
    // Initialize the timer for 1 ms intervals
22
    SysTick_Config(SystemCoreClock/1000);
23

24
    // Switch the FPU on
25
    SCB->CPACR = 0x00F00000;
26

27
    printf("Start\n");
28
    delay_ms(100);
29

30
    while (1)
31
    {
32
        // LED an
33
        WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BS_5);
34

35
        test_float();
36

37
        // LED aus
38
        WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BR_5);
39
        delay_ms(500);
40
    }
41
}

> 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
F303 mit FPU
2
f=2555.784180, duration=22 ms
3
d=2540.327709, duration=299 ms
4

5
F103 ohne FPU
6
f=2555.784180, duration=210 ms
7
d=2540.327709, duration=325 ms

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.
#6620241
Lesenswert?

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
Gast #6620261
Lesenswert?

Ich glaube zu erkennen, warum mein erstes Programm schon vor der ersten 
Zeile (init_clock) abstürzte:
1
08000dd0 <main>:
2

3
    printf("d=%f, duration=%ld ms\n",d, systick_count-start);
4
}
5

6
int main()
7
{
8
 8000dd0:  e92d 4ff0   stmdb  sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
9
 8000dd4:  ed2d 8b02   vpush  {d8}
10
 8000dd8:  b083        sub  sp, #12
11
    init_clock();
12
 8000dda:  f7ff ff63   bl  8000ca4 <init_clock>
13
    init_io();
14
 8000dde:  f7ff ffb3   bl  8000d48 <init_io>
15
...

Der Knackpunkt ist hier wohl die vpush Anweisung (Push extension 
registers onto the stack). Wenn ich den if-Block auskommentiere entfällt 
diese Anweisung.
#6620267
Lesenswert?

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
void initFPU(){
3
    SCB->CPACR = 0x00F00000;
4
}
Funktionieren. Die Funktion sollte der GCC vor Eintritt in die main 
schon aufrufen.
Gast #6620270
Lesenswert?

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().
Gast #6620281
Lesenswert?

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.
Gast #6620474
Lesenswert?

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.
Gast #6620478
Lesenswert?

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.
Gast #6621398
Lesenswert?

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.
Gast #6621469
Lesenswert?

Stefan ⛄ F. schrieb:
> vpush

Alex 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...
Gast #6621520
Lesenswert?

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.
Gast #6621556
Lesenswert?

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.
Gast #6621598
Lesenswert?

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?:
1
int main()
2
{
3
    init_clock();
4
    init_io();
5

6
    // Initialize the timer for 1 ms intervals
7
    SysTick_Config(SystemCoreClock/1000);
8

9
    printf("Start\n");
10
    delay_ms(100);
11

12
    while (1)
13
    {
14
        // LED an
15
        WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BS_5);
16

17
        uint32_t start=systick_count;
18
        float f=1.0;
19
        for (int i=0; i<10000; i++)
20
        {
21
             f=f*1.001;
22
             //if (f>100000.0)
23
             //{
24
             //    f=1.0;
25
             //}
26
        }
27
        printf("f=%f, duration=%ld ms\n",f, systick_count-start);
28

29
        // LED aus
30
        WRITE_REG(GPIOA->BSRR, GPIO_BSRR_BR_5);
31
        delay_ms(500);
32
    }
33
}
Da ist ja immer noch die Nutzung der FPU drin!
Angehängte Dateien:
Gast #6621635
Lesenswert?

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.
Gast #6621643
Lesenswert?

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.
#6621671
Lesenswert?

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.
Gast #6621697
Lesenswert?

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?
#6621767
Lesenswert?

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 ;)
Gast #6621923
Lesenswert?

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.
#6622274
Lesenswert?

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"

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren