STM32F4 : Gleitkommabefehle zu langsam

Ich arbeite an einer Audioanwendung auf dem Nucleo F411RE und habe festgestellt, dass meine Verarbeitung zu langsam war, wodurch die Anwendung einige Samples übersprang.

Wenn ich mir meine Demontage anschaue, dachte ich, dass es angesichts der Anzahl der Anweisungen und des 100-MHz-CPU-Takts (den ich in STM32CubeMx eingestellt habe) viel schneller sein sollte.

Ich habe den SYSCLK-Wert überprüft und er ist wie erwartet 100 MHz. Um 100% sicher zu sein, habe ich 1000 "nop" in meine Hauptschleife gelegt und 10 µs gemessen, was einem 100-MHz-Takt entspricht.

Ich habe genau die Zeit gemessen, die meine Verarbeitung benötigt, und sie dauert 14,5 µs, dh 1450 Taktzyklen. Ich denke, es ist viel zu viel, wenn man bedenkt, dass die Verarbeitung ziemlich einfach ist:

for(i=0; i<12; i++)
{
    el1.mode[i].phase += el1.osc[i].phaseInc;  // 16 µs
    if(el1.osc[i].phase >= 1.0) // 20 µs (for the whole "if"
        el1.osc[i].phase -= 1.0; 
    el1.osc[i].value = sine[ (int16_t)(el1.osc[i].phase * RES) ]; // 96 µs
    el1.val += el1.osc[i].value * el1.osc[i].amp; // 28 µs
} // that's a total of 1.63 µs for the whole loop

wobei phase und phaseInc Floats mit einfacher Genauigkeit sind und value ein int16_t ist, ist sine[] eine Nachschlagetabelle, die 1024 int16_t enthält.

Es sollten nicht mehr als 500 Zyklen sein, oder? Ich habe mir die Disassemblierung angesehen, sie verwendet die Gleitkommaanweisungen ... Zum Beispiel lautet die Disassemblierung der letzten Zeile: vfma.f32 => 3 Zyklen vcvt.s32.f32 => 1 Zyklus vstr => 2 Zyklen ldrh.w = > 2 Zyklen

(zykliert das Timing entsprechend ) Das sind also insgesamt 8 Anweisungen für diese Zeile, die die "größte" ist . Ich verstehe nicht wirklich, warum es so langsam ist ... Vielleicht, weil ich Strukturen verwende oder so?

Wenn jemand eine Idee hat, würde ich mich freuen, sie zu hören.

EDIT : Ich habe gerade die Zeit Zeile für Zeile gemessen, Sie können es im obigen Code sehen. Es scheint, als wäre die Nachschlagetabellenzeile die zeitaufwändigste Zeile, was bedeuten würde, dass die Speicherzugriffszeit kritisch ist? wie könnte ich das verbessern?

EDIT2: Demontage, wie von BruceAbott angefordert (sorry, es ist ein bisschen chaotisch, wahrscheinlich wegen der Art und Weise, wie es vom Compiler optimiert wurde):

membrane1.mode[i].phase += membrane1.mode[i].phaseInc;
0800192e:   vldr s14, [r5, #12]
08001932:   vldr s15, [r5, #8]
08001936:   vadd.f32 s15, s15, s14
0800193a:   adds r5, #24
179 if(membrane1.mode[i].phase >= 1.0)
0800193c:   vcmpe.f32 s15, s16
08001940:   vmrs APSR_nzcv, fpscr
180 membrane1.mode[i].phase -= 1.0;
08001944:   itt ge
08001946:   vmovge.f32 s14, #112    ; 0x70
0800194a:   vsubge.f32 s15, s15, s14
0800194e:   vstr s15, [r5, #-16]
182 membrane1.mode[i].value = sine[(int16_t)(membrane1.mode[i].phase * RES)];
08001952:   ldr.w r0, [r5, #-16]
08001956:   bl 0x80004bc <__extendsfdf2>
0800195a:   ldr r3, [pc, #112]      ; (0x80019cc <main+428>)
0800195c:   movs r2, #0
0800195e:   bl 0x8000564 <__muldf3>
08001962:   bl 0x8000988 <__fixdfsi>
08001966:   ldr r3, [pc, #104]      ; (0x80019d0 <main+432>)
184 membrane1.val += membrane1.mode[i].value * membrane1.mode[i].amp;
08001968:   vldr s13, [r5, #-4]
182 membrane1.mode[i].value = sine[(int16_t)(membrane1.mode[i].phase * RES)];
0800196c:   sxth r0, r0
0800196e:   ldrh.w r3, [r3, r0, lsl #1]
08001972:   strh.w r3, [r5, #-8]
184 membrane1.val += membrane1.mode[i].value * membrane1.mode[i].amp;
08001976:   sxth r3, r3
08001978:   vmov s15, r3
0800197c:   sxth r3, r4
0800197e:   vcvt.f32.s32 s14, s15
08001982:   vmov s15, r3
08001986:   vcvt.f32.s32 s15, s15
174 for(i=0; i<12; i++) // VADD.F32 : 1 cycle
0800198a:   cmp r5, r6
184 membrane1.val += membrane1.mode[i].value * membrane1.mode[i].amp;
0800198c:   vfma.f32 s15, s14, s13
08001990:   vcvt.s32.f32 s15, s15
08001994:   vstr s15, [sp, #4]
08001998:   ldrh.w r4, [sp, #4]
0800199c:   bne.n 0x800192e <main+270>
Haben Sie die Speicherzugriffszeiten berücksichtigt?
Nun, das ist nicht so einfach, sin(x) 12 Mal zu berechnen
Nicht wirklich... Um ehrlich zu sein, bin ich wirklich ein Anfänger in Sachen Optimierung... Aber könnte die Zugriffszeit die Zeit, die für meine Verarbeitung benötigt wird, so sehr erhöhen? Und wie könnte ich das optimieren?
@MarkoBuršič Entschuldigung, wenn mein Code nicht klar war, sine[] ist eine Wavetable, keine Funktion
Ist die FPU (Floating Point Unit) des Controllers aktiviert?
Es ist, ich habe '-mfloat-abi=hard -mfpu=fpv4-sp-d16' zu meinen Compiler-Befehlen hinzugefügt
@JonathanWheeler Ich habe mir das Datenblatt für das Teil angesehen, es ist ein Teil mit 0 Wartezustand. Ich gehe davon aus, dass Sie die Pin-Toggle-Methode verwenden, um Ihre Schleife zu bewerten. Verwenden Sie dieselbe Methode, um jedes Teil zu überprüfen. Setzen Sie einen Pin Toggle um die innere ifAussage. Wenn das kurz ist, versuchen Sie es in der Sinus-Nachschlagetabelle zu platzieren. Bald...
@slightlynybbled Vielen Dank, ich werde das gleich versuchen
Können Sie uns die Demontageliste zeigen?
@BruceAbbott Ich werde meinen Beitrag bearbeiten
Als Audio-Typ: bitte bitte bitte eine Ganzzahl für die Phase verwenden. Sie werden keinen Jitter ansammeln, und Sie erhalten den Wraparound kostenlos, wenn Sie die richtige Größe auswählen und einige feinere Details des C-Standards ignorieren. Dadurch wird alles auch in ganzzahlige Operationen umgewandelt, sodass die gesamte Schleife nicht länger als ein paar Zyklen dauern sollte. Dies ist jedoch keine Antwort auf die Frage, da Sie nicht gefragt haben, wie Sie optimieren können. :)
danke @pipe, gute idee! Ich werde das tun, ich messe erneut, um zu sehen, ob es die Dinge genug befestigt !! Ich habe Floats verwendet, weil ich zuerst interpoliert habe, aber die Interpolation war zu langsam, also musste ich sie fallen lassen
Nun, ich habe phase und phaseInc in uint16_t geändert, aber es hat die Zeit nicht geändert ... Vielleicht habe ich es falsch gemacht. Außerdem beträgt die membrane1.mode[i].value = sine[membrane1.mode[i].phase];längste Zeitdauer (0,96 µs). gibt es eine möglichkeit das zu optimieren?
EDIT: Irgendwann habe ich es geschafft, es dauert jetzt nur noch 5 µs dank @pipe ... vielen Dank Jungs !!!
Die Uhr auf einem MCU schneller zu stoßen, lässt die Dinge nicht schneller laufen, der Flash erfordert oft mehr Wartezustände, was alles verlangsamt. Einmal abgerufen und wenn der Prozessor sicher gebunden ist, aber wenn der Flash gebunden ist, dann nein. Idealerweise möchten Sie knapp unter der Geschwindigkeitsgrenze sein, wo Sie in den nächstgrößeren Wartezustand wechseln müssen.
@dwelch Ich habe überhaupt keine Erfahrung mit dem Thema und habe Ihren letzten Satz nicht wirklich verstanden ... Haben Sie Ressourcen zu diesem Thema? Danke!
Ich glaube, ich habe in einer dieser SO-Antworten einen Roman geschrieben. und so bei github. Angenommen, Ihr Blitz hat einen Wartezustand unter oder bei 24 MHz und zwei von 24 bis 32 und drei von 32 aufwärts. Bei 24 MHz können Sie also grundsätzlich mit 12 MHz auf den Flash zugreifen, aber knapp über 24 sind es 8 MHz, da Sie diesen nächsten Wartezustand benötigen. aber wie kommst du auf meine Scheinzahlen. Bei 32 Schwänzen beträgt der Blitz 10,7 MHz, aber knapp über 8 MHz. Bisher ist 24 der schnellste Zugriff auf den Flash (dies sind erfundene Zahlen). Der Blitz wird mit einer gewissen Höchstgeschwindigkeit bewertet, und dort werden diese Taktgrenzen gefunden
Die Wartezustandsanforderungen haben Grenzen bei Taktgeschwindigkeiten, die auf dem für diesen MCU entworfenen Flash basieren. Mein Punkt ist also Ihre Aussage, dass Sie 100 MHz verwenden, um es viel schneller zu machen, es möglicherweise nicht nur nicht schneller, sondern auch langsamer zu machen, je nachdem, wo 100 MHz landet und ob Sie Flash-gebunden oder prozessorgebunden sind. darum ging es in meinem kommentar. Ich habe nicht alle anderen Posts gelesen, aber die Antwort in Bezug auf 64-Bit-Float, Sie haben 1.0 anstelle von 1.0F verwendet, um ein Soft-Float zu erzwingen, was Sie einen großen Sprung in der Größe der Binärdatei hätten sehen sollen, hilft Ihnen zumindest nicht weiter Leistung.
Falls die Langsamkeit (teilweise) auf Flash-Zugriffe zurückzuführen ist, können Sie versuchen, die Funktion in den Arbeitsspeicher zu packen: stackoverflow.com/questions/15137214/…
@dwelch ein kleines Update zu Ihrem Rat: Ich habe mir die Wartezustände für die STM32F4-Serie angesehen und es stellt sich heraus, dass es sich bei jeder Taktfrequenz um einen Wartezustand von 0 handelt. Sie führen mich jedoch dazu, diese ganze Angelegenheit kennenzulernen, und ich werde sie bei meinen späteren Projekten berücksichtigen. ich danke Ihnen sehr! Michael : Ja, das ist eine gute Idee, dein Link ist sehr interessant, obwohl dort kein Feedback gegeben wird. Ich werde meine geben, sobald ich dies in Anwendung stelle! Danke.
Ja, immer prüfen...

Antworten (1)

In Ihrer Disassemblierung sehen wir Aufrufe von mathematischen 64-Bit-Funktionen (doppelte Genauigkeit): -

08001956:   bl 0x80004bc <__extendsfdf2>
...
0800195e:   bl 0x8000564 <__muldf3>
08001962:   bl 0x8000988 <__fixdfsi>

Der STM32F4 unterstützt nur 32-Bit-Gleitkomma in der Hardware, daher müssen diese Funktionen in der Software ausgeführt werden und benötigen viele Zyklen zur Ausführung. Um sicherzustellen, dass alle Berechnungen in 32 Bit durchgeführt werden, sollten Sie alle Ihre Fließkommazahlen (einschließlich Konstanten) als Typ definieren float.

Der OP-Code ist so geschrieben, dass er doppelt verwendet wird, daher ist dies sinnvoll. Beginnen Sie damit, die 1.0 auf 1.0F zu ändern, ohne dass sie doppelt pro Sprachstandard und die Dinge drumherum befördert werden müssen.
Ich habe versucht, meine RES-Konstante auf 1024.0f zu ändern, und es hilft sehr. Vielen Dank an euch zwei! Ich hatte keine Ahnung, dass der Sprachstandard alle Floating-Konstanten zu einem Double macht und alles fördert ... Gibt es eine Möglichkeit, dieses Verhalten zum Beispiel mit Compiler-Optionen zu ändern?
das wäre Compiler-spezifisch, vielleicht, vielleicht auch nicht. aber innerhalb der Sprache verwenden Sie einfach das F, um einen einzelnen Float anzugeben. oder vielleicht Typumwandlung (Float) (1.0), der Compiler sollte die Konvertierung optimieren und es zur Kompilierzeit und nicht zur Laufzeit machen.