Multiply-Add Instruktion ist langsam

OP #5817152
Ich programmiere derzeit auf dem teensy 36 Board, das hat ne ARM Cortex 
M4 CPU und bei den Fliesspunktbefehlen habe ich folgendes festgestellt:
Addition (Befehl VADD) und Multiplikation(Befehl VMUL) brauchen jweils 1 
Zyklus. Die Multiply-Add Instruction (VMLA) braucht 3 Zyklen. Damit ist 
der VMLA Befehl langsamer als wenn ich getrennt MUL und ADD mache. 
Welchen Sinn ergibt das? Welchen Vorteil hat die VMLA Instruktion?

Im folgenden Beispiel
1
 for(int k=0 ; k<nn ; k++){
2
    fx += fy * fz ;
3
    fy += 1.2 ;
4
    fz += 1.3 ;
5
    }
benutzt der GCC compiler die VMLA Instruktion für fx += fy * fz ;
#5817165
Das Ergebnis ist genauer, wenn erst abschliessend gerundet wird:
https://de.wikipedia.org/wiki/Fused_multiply-add

Es wird ein Zwischenregister eingespart.

PS: Lies auch die Fussnoten: "Floating-point arithmetic data processing 
instructions, such as add, subtract, multiply, divide, square-root, all 
forms of multiply with accumulate, as well as conversions of all types 
take one cycle longer if their result is consumed by the following 
instruction."
  FMUL
  FADD
  ..use result..
hat 2 Straftakte drin und kommt damit auf 4 Takte, während
  VMLA
  ..use result..
nur einen Straftakt hat und damit gleichauf liegt.
OP #5817377
Das klingt schon mal logisch. Besten Dank.

Ich habe die Reihenfolge der Statements in der Schleife jetzt mal 
geändert (siehe unten). Ich dachte ich könnte so einen Strafzyklus 
notwendig machen, weil der vfma Befehl jetzt das Resultat des vadd 
Befehls vor ihm braucht. Aber der Compiler hat geschickterweise die subs 
Instruktion vorgezogen.
1
  int32_t start=micros() ;
2
  for(int k=0 ; k<nn ; k++){ 
3
    fy += 1.2 ;
4
    fz += 1.3 ;
5
    fx += fy * fz ;
6
    }
7
  int32_t stop=micros() ;
8
/*
9
   50e: ee37 7a06   vadd.f32  s14, s14, s12    1 Zyklus
10
   512: ee77 7aa5   vadd.f32  s15, s15, s11    1 Zyklus
11
   516: 3b01        subs  r3, #1               1 Zyklus
12
   518: eee7 6a27   vfma.f32  s13, s14, s15    3 Zyklen
13
   51c: d1f7        bne.n 50e <setup+0x9e>     2 Zyklen
14
*/

Jetzt verstehe ich folgendes nicht: Meine Laufzeitmessung sagt, dass ein 
Schleifendurchlauf 7 Zyklen braucht. Ich komme aber auf 8 Zyklen, wo 
mache ich einen Fehler ?
#5817403
void schrieb:
> Wilde Vermutung: bne.n hat unterschiedliche Ausführungszeit für branch
> taken und branch not taken

Die hat er mit 2 Takten schon eingerechnet. Aber ohne nun beim M4 
konkret nachgesehen zu haben könnte es sein, dass das Gesamtsystem ihm 
noch einen Takt für nichtsequentiellen Zugriff oder schlecht aligntes 
Sprungziel zusätzlich drauf haut.
Gast #5817415
A. K. schrieb:
> Die hat er mit 2 Takten schon eingerechnet. Aber ohne nun beim M4
> konkret nachgesehen zu haben

Ja, und wenn er für branch taken nur einen Takt braucht. So herum war es 
gemeint. War halt eine Idee zum nachschauen. Das nehme ich ihm aber 
nicht ab.
Gast #5817418
Sorry für OT, aber warum schaust du dir das so genau an? Ich meine, es 
ist interessant aber irgendwie auch brotlos auf den größeren 
Controllern. Mich würde es einfach interessieren.
#5817540
Die Pipeline widmet sich bereits dem Sprungbefehl und dessen Ziel, 
während der VMLA Befehl noch läuft. Das ist zwar spekulativ, aber 
dadurch reduziert sich die Laufzeit der Execute-Stage des Sprungbefehls 
auf einen Takt zur Bestätigung.

https://community.arm.com/developer/ip-products/processors/f/cortex-m-forum/8969/question-about-the-pipeline-clock-cycle-and-machine-cycle-in-cortex-m-series
Gast #5817685
Martin O. schrieb:
> Welchen Sinn ergibt das? Welchen Vorteil hat die VMLA Instruktion?

Normalerweise macht man damit Filter und optimiert selbige.

Das heißt - da man ohnehin beim Filtern 2 Datenströme bearbeiten muß (I 
und Q) - kann man das Ganze auch verschachteln und die Schleife zum Teil 
aufrollen, womit man den Taktverbrauch für das Schleifenzählen und 
Springen reduziert.

Also mal ganz grobschlächtig skizziert so:
 for ..
{ sumI = sumI + pkoeffI++ * psamplI++;
  sumQ = sumQ + pkoeffQ++ * psamplQ++;
  sumI = sumI + pkoeffI++ * psamplI++;
  sumQ = sumQ + pkoeffQ++ * psamplQ++;
  ...usw. immer I und Q abwechselnd
}
Wenn man z.B. in der Schleife 8 solche IQ Paare drin hat, dann ist der 
Aufwand in Takten für das Schleifenzählen nur noch 1/8 dessen, was man 
mit nur einem Paar braucht.

W.S.
OP #5817868
In anderen Beispielen benötigte die CPU immer 2 Takte für den Sprung. 
Daher zähle ich den Sprung hier auch mit 2 Zyklen.

Das ganze mache ich aus purer Neugirde und um herauszufinden welche 
Konstrukte welchen Aufwand haben.

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über 7 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.