SSE Beschleunigung

Für allgemeine Fragen zur Programmierung mit PureBasic.
SMaag
Beiträge: 150
Registriert: 08.05.2022 12:58

SSE Beschleunigung

Beitrag von SMaag »

Das 3DDrawing von STARGATE
viewtopic.php?t=22128&hilit=3DDrawing
hat mich inspiriert, die SSE Beschleunigung unter die Lupe zu nehmen.
Eine grundsätzliche Beschreibung dazu findet man unter https://wiki.freepascal.org/SSE/de.

Mir ging es um die Beschleunigung, die man damit evtl. rausholen kann.
Ich habe die klassische Version Vector4-Addition als Macro mit der SSE Version als Procedure verglichen.
Es ging mir darum, zu sehen, ob SSE trotz zusätzlichem Prodecdure-Aufruf schneller ist als die klassiche Version
ohne Procedure-Aufruf.
Ich habe noch versucht, die SSE-Version auch in ein Macro zu verpacken. Aber das hat nicht geklappt.
Falls jemand weis, ob und wie das evtl. geht. Bitte mitteilen.

hier der Testcode

Code: Alles auswählen

EnableExplicit

Structure TVec4f 
  v.f[4] 
EndStructure

#SSE_USE_SSE = #True

Macro macVec_Add(v0, v1, Res)
    v0\v[0]= v0\v[0] + v1\v[0]   
    v0\v[1]= v0\v[1] + v1\v[1]   
    v0\v[1]= v0\v[2] + v1\v[2]   
    v0\v[1]= v0\v[3] + v1\v[3]        
EndMacro


Procedure Vec_Add(*v0.TVec4f, *v1.TVec4f, *Res.TVec4f) 
  
  ; Result[0] = v0[0] + v1[0]
  ; Result[1] = v0[1] + v1[1]
  ; Result[2] = v0[2] + v1[2]
  ; Result[3] = v0[3] + v1[3]
  
  CompilerIf #SSE_USE_SSE
    
    CompilerIf  #PB_Compiler_64Bit    
      !Mov rax, [p.p_v0]
    	!Mov rdx, [p.p_v1]´
      !Movups  Xmm0, [rax]
      !Movups  Xmm1, [rdx]
      !Addps   Xmm1, Xmm0
      !Mov eax, [p.p_Res]
      !Movups  [rax], Xmm1      
    CompilerElse          
      !Mov eax, [p.p_v0]
    	!Mov edx, [p.p_v1]
      !Movups  Xmm0, [eax]
      !Movups  Xmm1, [edx]
      !Addps   Xmm1, Xmm0
      !Mov eax, [p.p_Res]
      !Movups  [eax], Xmm1
    CompilerEndIf
      
  CompilerElse    
    With *res
      \v[0]= v0\v[0] + v1\v[0]   
      \v[1]= v0\v[1] + v1\v[1]   
      \v[1]= v0\v[2] + v1\v[2]   
      \v[1]= v0\v[3] + v1\v[3]        
    EndWith    
  CompilerEndIf
  
EndProcedure

Define.i t1, t2, I, J
  
Define.TVec4f A, B, C

A\v[0] = 1
A\v[1] = 2
A\v[2] = 3
A\v[3] = 4

B\v[0] = 1
B\v[1] = 2
B\v[2] = 3
B\v[3] = 4


#cstRounds = 10000000

t1 = ElapsedMilliseconds()
For I = 0 To #cstRounds
  Vec_Add(A, B, C)
Next
t1 = ElapsedMilliseconds() - t1

t2 = ElapsedMilliseconds()
For I = 0 To #cstRounds
  macVec_Add(A, B, C)
Next
t2 = ElapsedMilliseconds() - t2

Debug C\v[0]
Debug C\v[1]
Debug C\v[2]
Debug C\v[3]

MessageRequester("Execution times", "SSE   : = " + t1 + #CRLF$ + "Classic : = " + T2)

Ergebnis bei mir:
mit Debuger: SSE = 1104ms classic = 621
ohne Debugger: SSE=12ms classic = 57

D.h. mit Debugger bremst der zusätzliche Procedure-Aufruf aus.
Ohne Debugger gewinnt die SSE-Version um fast den Faktor 5.
Im Free-Pascal Wiki ist eine Beschleunigung von 8-9 angegeben.
D.h. der Procedrue-Aufruf schluckt noch 3-4!

Für das C-Backend kann man das leider nicht verwenden!
Ich gehe aber davon aus, dass der C-Compiler evtl. MMX und SSE Regisster
besser automatisch optimieren kann. Das könnte der Grund sein, warum die Framerates
bei Verwendung des C-Backends teilweise deutlich höher sind.
SMaag
Beiträge: 150
Registriert: 08.05.2022 12:58

Re: SSE Beschleunigung; ASM Macro! Warum geht das so???

Beitrag von SMaag »

Ich hab's jetzt nach langem rumprobieren geschafft, die ASM-SSE Befehle in
ein Macro zu verpacken. Wie erwartet ist das auch mit Debugger schneller
und ohne Debugger stellt sich dann der erwartete Faktor 8 ein (bei mir ca. 8.2 CPU=Ryzen7 )

Aber kann mir mal einer erklären, warum man das genau so machen muss!
Bzw. was ist der Unterschied bei ASM mit dem '!'

hier das Macro für 64/32Bit : das schiebt aber als Ergebnis nus 1.0, 2.0, 3.0, 4.0 durch, da wird nichts addiert!

Code: Alles auswählen

Macro mac_SSE_Vec_Add(v0, v1, Res)
  EnableASM
  CompilerIf  #PB_Compiler_64Bit    
     MOV rax, v0
     MOV rdx, v1
     !MOVUPS Xmm0, [rax]
     !Movups Xmm1, [rdx]
     !Addps  Xmm1, Xmm0
     MOV rax, Res
     !Movups  [rax], Xmm1   
  CompilerElse  ; 32Bit   
     MOV eax, v0			; hier muss der Pointer von A landen
     MOV edx, v1			; hier muss der Pointer von B landen
     !MOVUPS Xmm0, [eax]
     !MOVUPS Xmm1, [edx]
     !MOVUPS Xmm1, Xmm0
     MOV eax, Res 		; hier muss der Pointer von C landen
     !Movups  [eax], Xmm1
   CompilerEndIf
   DisableASM 
EndMacro
hier der komplette Testcode

Code: Alles auswählen

EnableExplicit

Structure TVec4f 
  v.f[4] 
EndStructure

Structure TMatrixf
  m.TVec4f[4]  
EndStructure

#SSE_USE_SSE = #True

Macro mac_Vec_Add_classic(v0, v1, Res)
;   mit diesem Fehler, dass v0 als Reult statt verwendet wurde, ist das mehr als 2x langsamer   
;     v01\v[0]= v0\v[0] + v1\v[0]   
;     v0\v[1]= v0\v[1] + v1\v[1]   
;     v0\v[2]= v0\v[2] + v1\v[2]   
;     v0\v[3]= v0\v[3] + v1\v[3]    
    
     Res\v[0]= v0\v[0] + v1\v[0]   
     Res\v[1]= v0\v[1] + v1\v[1]   
     Res\v[2]= v0\v[2] + v1\v[2]   
     Res\v[3]= v0\v[3] + v1\v[3]        

EndMacro
  
;   CompilerIf #PB_Compiler_32Bit    
;     Macro rax
;       eax
;     EndMacro   
;   CompilerEndIf
    
Macro mac_SSE_Vec_Add(v0, v1, Res)
  EnableASM
  CompilerIf  #PB_Compiler_64Bit    
     MOV rax, v0
     MOV rdx, v1
     !MOVUPS Xmm0, [rax]
     !Movups Xmm1, [rdx]
     !Addps  Xmm1, Xmm0
     MOV rax, Res
     !Movups  [rax], Xmm1   
  CompilerElse  ; 32Bit   
     MOV eax, v0
     MOV edx, v1
     !MOVUPS Xmm0, [eax]
     !MOVUPS Xmm1, [edx]
     !MOVUPS Xmm1, Xmm0
     MOV eax, Res
     !Movups  [eax], Xmm1
   CompilerEndIf
   DisableASM 
EndMacro

Procedure Vec_Add(*v0.TVec4f, *v1.TVec4f, *Res.TVec4f) 
  
  ; Result[0] = v0[0] + v1[0]
  ; Result[1] = v0[1] + v1[1]
  ; Result[2] = v0[2] + v1[2]
  ; Result[3] = v0[3] + v1[3]
  
  CompilerIf #SSE_USE_SSE
    
    CompilerIf  #PB_Compiler_64Bit    
      !Mov rax, [p.p_v0]
    	!Mov rdx, [p.p_v1]´
      !Movups  Xmm0, [rax]
      !Movups  Xmm1, [rdx]
      !Addps   Xmm1, Xmm0
      !Mov eax, [p.p_Res]
      !Movups  [rax], Xmm1      
    CompilerElse          
      !Mov eax, [p.p_v0]
    	!Mov edx, [p.p_v1]
      !Movups  Xmm0, [eax]
      !Movups  Xmm1, [edx]
      !Addps   Xmm1, Xmm0
      !Mov eax, [p.p_Res]
      !Movups  [eax], Xmm1
    CompilerEndIf
      
  CompilerElse    
    ; mac_Vec_Add_classic (*v0, *v1m *res); für Test deaktiviert, dass auch sicher ASM-Code ausgeführt wird
  CompilerEndIf
  
EndProcedure

Define.i t1, t2, t3, I, J
  
Define.TVec4f A, B, C
Define.i pA, pB, pC

A\v[0] = 1
A\v[1] = 2
A\v[2] = 3
A\v[3] = 4

B\v[0] = 10
B\v[1] = 20
B\v[2] = 30
B\v[3] = 40

pA=@A\v[0]
pB=@B\v[0]
pC=@C\v[0]

#cstRounds = 10000000

; ----------------------------------------------------------------------
;  SSE Macro Version
; ----------------------------------------------------------------------

C\v[0] = 0
C\v[1] = 0
C\v[2] = 0
C\v[3] = 0

t1 = ElapsedMilliseconds()
For I = 0 To #cstRounds
  mac_SSE_Vec_Add(A, pB, pC)    ; die Macro-Version => noch schneller Faktor 8 gegenüber Classic
Next
t1 = ElapsedMilliseconds() - t1
Debug "SSE-Macro " + t1 + "ms" 
Debug A\v[0]
Debug A\v[1]
Debug A\v[2]
Debug A\v[3]

; Ergebnis löschen
C\v[0] = 0
C\v[1] = 0
C\v[2] = 0
C\v[3] = 0

; ----------------------------------------------------------------------
;  SSE Procedure Version
; ----------------------------------------------------------------------

t2 = ElapsedMilliseconds()
For I = 0 To #cstRounds
  Vec_Add(A, B, C)
Next
t2 = ElapsedMilliseconds() - t2
Debug "SSE-Procedure " + t2 + "ms" 
Debug C\v[0]
Debug C\v[1]
Debug C\v[2]
Debug C\v[3]

; Ergebnis löschen
C\v[0] = 0
C\v[1] = 0
C\v[2] = 0
C\v[3] = 0

; ----------------------------------------------------------------------
;  Classic Version Macro
; ----------------------------------------------------------------------

t3 = ElapsedMilliseconds()
For I = 0 To #cstRounds
  mac_Vec_Add_classic(A, B, C)
Next
t3 = ElapsedMilliseconds() - t3

Debug "Classic-Version " + t3 + "ms" 
Debug C\v[0]
Debug C\v[1]
Debug C\v[2]
Debug C\v[3]

MessageRequester("Execution times", "SSE Macro   : = " + t1 + #CRLF$ + "SSE Procedure : = " + T2 + #CRLF$ + "Classic : = " + T3)

Zuletzt geändert von SMaag am 04.12.2022 21:34, insgesamt 1-mal geändert.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6996
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: SSE Beschleunigung

Beitrag von STARGÅTE »

ist noch ein wenig verbugt dein code und er läuft nur unter x86.
Für x64 musst noch noch ein paar Abfragen einbauen. Und die Register richtig nennen.
Aktuell steht selbst im x64 Code-Block noch "eax".
Edit: Außerdem schreibst du garnicht in den Output-Vektor rein, bei der Classic version! Und die Dimensionsindizes sind falsch.

Zu der Sache mit ASM und dem "!". ASM Code mit "!" wird 1:1 übernommen, daher müssen die Variabel Namen richtig geschrieben werden. In einer Procedure zB. p.p_...
Ohne das "!" funkt PureBasic noch dazwischen, daher kannst du den PB Variabel Namen direkt hinter das MOV schreiben.

Wie stabil ist diese Macro jetzt?
Also ich hab ein bisschen rumprobiert und irgendwie kann ich keine Strukturen benutzen, wie ich es in meinem Include nutze, da erhalte ich IMA 0

Code: Alles auswählen

V4_Add(Drawing3DInclude\Position, Vector)
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
SMaag
Beiträge: 150
Registriert: 08.05.2022 12:58

Re: SSE Beschleunigung

Beitrag von SMaag »

@STARGATE

ich bin noch komplett am rumexperimentieren. Das ist noch nicht einsatzfähig.
Ich hoffe noch auf kompetente Hilfe!

Bisher ist mir nicht einmal ganz klar, warum das Macro nur so funktioniert,
MOV eax, v0
mit MOV eax, [v_v0] und allen ähnlichen Konstrukten geht es nicht [p_v0], [p.v0]

Ziel ist es, das entsprechend stabil hinzubekommen und zu wissen, wann man es wie man es machen muss.
Dann das ganze in ein Modul packen!
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6996
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: SSE Beschleunigung

Beitrag von STARGÅTE »

Variable: v_<name>
Pointer: p_<name>
Variable in Prozedur: p.v_<name>
Pointer in Prozedur: p.p_<name>
Das geht aber nur, wenn <name> tatsächlich eine Variable/Pointer ist. Beim Macro wird ja "blöderweise" einfach der Parameter durchgeschleust und dann kommt da natürlich kauderwelsch.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
SMaag
Beiträge: 150
Registriert: 08.05.2022 12:58

Re: SSE Beschleunigung

Beitrag von SMaag »

ich hab den Code oben ein Update verpasst!

Das SSE Macro geht leider gar nicht. Es läuft zwar durch, addiert aber nicht!

Und ein äußerst interessantes Detail ist beim Classic Macro aufgetaucht
Ich hatte einen Fehler drin, und das Ergebnis wurde nicht auf den 3ten Parameter Res zurück geschrieben sondern
auf v0.
Wenn man nur 2 Parameter verwendet und auf einen wieder zurück schreibt, dauert das mehr als doppelt so lange,
wie mit 3 Parametern. 56 statt 21ms

Damit schwindet der Vorteil von SSE gegenüber dem Classic Macro
auf (ohne Debugger)
SSE Macro = 68ms ; addiert aber nicht!
SSE Procedure = 148ms
Classic_Macro = 199ms

Code: Alles auswählen

Macro mac_Vec_Add_classic(v0, v1, Res)
;   mit diesem Fehler, dass v0 als Reult verwendet wurde, ist das mehr als 2x langsamer   
;     v01\v[0]= v0\v[0] + v1\v[0]   
;     v0\v[1]= v0\v[1] + v1\v[1]   
;     v0\v[2]= v0\v[2] + v1\v[2]   
;     v0\v[3]= v0\v[3] + v1\v[3]    
    ; mit extra Result-Parameter ist das mehr als doppelt so schnell
     Res\v[0]= v0\v[0] + v1\v[0]   
     Res\v[1]= v0\v[1] + v1\v[1]   
     Res\v[2]= v0\v[2] + v1\v[2]   
     Res\v[3]= v0\v[3] + v1\v[3]        

EndMacro

Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6996
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: SSE Beschleunigung

Beitrag von STARGÅTE »

SMaag hat geschrieben: 04.12.2022 21:40 Ich hatte einen Fehler drin, und das Ergebnis wurde nicht auf den 3ten Parameter Res zurück geschrieben sondern
auf v0.
Das hatte ich geschrieben. Außerdem geht der code immer noch nicht in x64, weil da immer noch !Mov eax, [p.p_Res] steht.
Und in Zeile 66 steht am Ende des Codes ein "´". Außerdem addiert dein Macro sehr wohl, du musst halt Debug C\v[0] angucken und nicht A.

Bei mir ist die SSE Procedure übrigens am schnellsten, sogar schneller als das Macro.
x64 hat geschrieben: SSE Macro : = 32
SSE Procedure : = 21
Classic : = 36
x86 hat geschrieben: SSE Macro : = 27
SSE Procedure : = 18
Classic : = 29
AMD Ryzen 9 3900X

Das Macro ist so oder so n ziemliche Krücke, zumal du dein pA=@A\v[0] eigentlich mit in die Zeitmessung nehmen müsstest ;-)
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
SMaag
Beiträge: 150
Registriert: 08.05.2022 12:58

Re: SSE Beschleunigung

Beitrag von SMaag »

Jetz nochmal der komplette Testcode.
Die Fehler im x64 Code behoben, ohne es testen zu können!

Das mit dem SSE Macro geht zwar, wenn man es richtig macht, bringt aber nichts!
Man benötigt für ASM unbedingt die direkten Pointer auf den Datenbereich.
Nimmt man das mit in das Macro rein, ist es langsamer als die Procedure

bei 10Mio. Aufrufen 32Bit
SSE Macro : 147ms
SSE Proc : 128ms
Classic Macro: 346ms
Classic Proc: 529ms

Der Code dient nur dazu, um rauszufinden, was denn die beste Methode ist.
Er ist nicht als letztendliche Anwendung gedacht!
PB liefert ab der V6.0 vordefinierte Strucktures für Vector3 und Vector4 mit.
Evtl. ist das schlauer, das gleich mit den PB-Structrues zu machen

Code: Alles auswählen

EnableExplicit

Structure TVec4f 
  v.f[4] 
EndStructure

Structure TMatrixf
  m.TVec4f[4]  
EndStructure

#SSE_USE_SSE = #True

Macro Clear_Vector(vec)
  vec\v[0] = 0  
  vec\v[1] = 0  
  vec\v[2] = 0  
  vec\v[3] = 0  
EndMacro

  
Macro mac_Vec_Add_classic(v0, v1, Res)
;   mit diesem Fehler, dass v0 als Result verwendet wurde, ist das mehr als 2x langsamer   
;     v01\v[0]= v0\v[0] + v1\v[0]   
;     v0\v[1]= v0\v[1] + v1\v[1]   
;     v0\v[2]= v0\v[2] + v1\v[2]   
;     v0\v[3]= v0\v[3] + v1\v[3]    
    
     Res\v[0]= v0\v[0] + v1\v[0]   
     Res\v[1]= v0\v[1] + v1\v[1]   
     Res\v[2]= v0\v[2] + v1\v[2]   
     Res\v[3]= v0\v[3] + v1\v[3]        

EndMacro
  
;   CompilerIf #PB_Compiler_32Bit    
;     Macro rax
;       eax
;     EndMacro   
;   CompilerEndIf


Macro mac_SSE_Vec_Add_1(v0, v1, Res)
  ; das geht leider nicht!!!
  EnableASM
  CompilerIf  #PB_Compiler_64Bit    
     MOV rax, v0
     MOV rdx, v1
     !MOVUPS Xmm0, [rax]
     !Movups Xmm1, [rdx]
     !Addps  Xmm1, Xmm0
     MOV rax, Res
     !Movups  [rax], Xmm1   
  CompilerElse  ; 32Bit   
     MOV eax, v0
     MOV edx, v1
     !MOVUPS Xmm0, [eax]
     !MOVUPS Xmm1, [edx]
     !Addps Xmm1, Xmm0
     MOV eax, Res
     !Movups  [eax], Xmm1
   CompilerEndIf
   DisableASM 
EndMacro

Macro mac_SSE_Vec_Add(IN1, IN2, RES)
  ; so geht's, zuerst benötigt man die Pointer!
  ; direkt mit den Struct-Variablen crashed das!
  ; So ist das Macro aber langsamer als die Procedure
  Protected *pIN1
  Protected *pIN2
  Protected *pRES
  
  *pIN1 = IN1
  *pIN2 = IN2
  *pRES = RES
  
  CompilerIf  #PB_Compiler_64Bit    
    !Mov rax,  [p.p_pIN1]
    !Mov rdx,  [p.p_pIN2]
    !Movups  Xmm0, [rax]
    !Movups  Xmm1, [rdx]
    !Addps   Xmm1, Xmm0
    !Mov rax, [p.p_pRES]
    !Movups  [rax], Xmm1
  CompilerElse          ; 32Bit    
    !Mov eax,  [p.p_pIN1]
    !Mov edx,  [p.p_pIN2]
    !Movups  Xmm0, [eax]
    !Movups  Xmm1, [edx]
    !Addps   Xmm1, Xmm0
    !Mov eax, [p.p_pRES]
    !Movups  [eax], Xmm1
  CompilerEndIf
EndMacro

Procedure SSE_Vec_Add(*v0.TVec4f, *v1.TVec4f, *Res.TVec4f) 
  
  ; Result[0] = v0[0] + v1[0]
  ; Result[1] = v0[1] + v1[1]
  ; Result[2] = v0[2] + v1[2]
  ; Result[3] = v0[3] + v1[3]
  
  CompilerIf #SSE_USE_SSE
    
    CompilerIf  #PB_Compiler_64Bit    
      !Mov rax, [p.p_v0]
    	!Mov rdx, [p.p_v1]
      !Movups  Xmm0, [rax]
      !Movups  Xmm1, [rdx]
      !Addps   Xmm1, Xmm0
      !Mov rax, [p.p_Res]
      !Movups  [rax], Xmm1      
    CompilerElse          ; 32Bit    
      !Mov eax, [p.p_v0]
    	!Mov edx, [p.p_v1]
      !Movups  Xmm0, [eax]
      !Movups  Xmm1, [edx]
      !Addps   Xmm1, Xmm0
      !Mov eax, [p.p_Res]
      !Movups  [eax], Xmm1
    CompilerEndIf
      
  CompilerElse    
    ; mac_Vec_Add_classic (*v0, *v1m *res); für Test deaktiviert, dass auch sicher ASM-Code ausgeführt wird
  CompilerEndIf
  
EndProcedure

Procedure Vec_Add_Classic_Proc(*v0.TVec4f, *v1.TVec4f, *Res.TVec4f)
  mac_Vec_Add_classic(*v0, *v1, *Res)
EndProcedure


Procedure Test()
  Define.i t1, t2, t3, t4, I, J
    
  Define.TVec4f A, B, C
  Define.TVec4f *pA, *pB, *pC
  
  A\v[0] = 1
  A\v[1] = 2
  A\v[2] = 3
  A\v[3] = 4
  
  B\v[0] = 10
  B\v[1] = 20
  B\v[2] = 30
  B\v[3] = 40
  
  *pA=A ; \v[0]
  *pB=B ; \v[0]
  *pC=C ; \v[0]
  Debug "Pointer"
  Debug "*pA     : " + *pA
  Debug "@A\v[0] : " + @A\v[0]
  Debug "@A      : " + @A
  Debug "A       : " + Str(A)
  Debug ""
  
  ; Anzahl Wiederholungen für die Zeitmessung
  #cstRounds = 10000000;0
  
  ; ----------------------------------------------------------------------
  ;  SSE Macro Version
  ; ----------------------------------------------------------------------
  
  Clear_Vector(C)
  
  t1 = ElapsedMilliseconds()
  For I = 0 To #cstRounds
    mac_SSE_Vec_Add(A, B, C)    ; die Macro-Version => noch schneller Faktor 8 gegenüber Classic     
  Next
  
;   EnableASM
;       !Mov eax, dword [p.p_pA]
;     	!Mov edx, dword [p.p_pB]
;       !Movups  Xmm0, [eax]
;       !Movups  Xmm1, [edx]
;       !Addps   Xmm1, Xmm0
;       !Mov eax, dword [p.p_pC]
;       !Movups  [eax], Xmm1
;   DisableASM
      
  t1 = ElapsedMilliseconds() - t1
  Debug "SSE-Macro " + t1 + "ms" 
  Debug C\v[0]
  Debug C\v[1]
  Debug C\v[2]
  Debug C\v[3]
  
  ; ----------------------------------------------------------------------
  ;  SSE Procedure Version
  ; ----------------------------------------------------------------------
  
  ; Ergebnis löschen
  Clear_Vector(C)
  
  t2 = ElapsedMilliseconds()
  For I = 0 To #cstRounds
    SSE_Vec_Add(A, B, C)
  Next
  t2 = ElapsedMilliseconds() - t2
  Debug "SSE-Procedure " + t2 + "ms" 
  Debug C\v[0]
  Debug C\v[1]
  Debug C\v[2]
  Debug C\v[3]
    
  ; ----------------------------------------------------------------------
  ;  Classic Version Macro
  ; ----------------------------------------------------------------------
  
  ; Ergebnis löschen
  Clear_Vector(C)

  t3 = ElapsedMilliseconds()
  For I = 0 To #cstRounds
    mac_Vec_Add_classic(A, B, C)
  Next
  t3 = ElapsedMilliseconds() - t3
  
  Debug "Classic-Macro " + t3 + "ms" 
  Debug C\v[0]
  Debug C\v[1]
  Debug C\v[2]
  Debug C\v[3]
  
   ; ----------------------------------------------------------------------
  ;  Classic Version Procedure
  ; ----------------------------------------------------------------------
  
  ; Ergebnis löschen
  Clear_Vector(C)

  t4 = ElapsedMilliseconds()
  For I = 0 To #cstRounds
    Vec_Add_Classic_Proc(A, B, C)
  Next
  t4 = ElapsedMilliseconds() - t4
  
  Debug "Classic-Procedure " + t4 + "ms" 
  Debug C\v[0]
  Debug C\v[1]
  Debug C\v[2]
  Debug C\v[3]
 
  
  MessageRequester("Execution times", "SSE Macro   : = " + t1 + #CRLF$ + "SSE Procedure : = " + T2 + #CRLF$ + "Classic Macro : = " + T3 + #CRLF$ + "Classic Procedure : = " + T4)
EndProcedure

Test()
SMaag
Beiträge: 150
Registriert: 08.05.2022 12:58

AVX Beschleunigung für Double Vectoren

Beitrag von SMaag »

für die Single-Float Vectoren hab ich das hingekommen.

Gleiches geht auch über die AVX-Extention für Double-Vectoren

leider klappt das bei mir nicht!
- Die beiden IN-Operanden landen noch korrekt in den YMM0/1 Registern.
nur bei der Addition kommt dann nicht das korrekte Ergebnis raus.

Debug Ausdruck; das müsste bei 32-Bit-Single und 64-Bit-Double das gleiche Ergebnis sein!
Es wird aber nur X und Z bearbeitet und ist auch noch falsch!
--------------------------------------------------------------
SyzeOf(TVector4d) = 32
Compiled Type : MMX_SSE_x32_ASM

32-Bit Float
11.0
22.0
33.0
44.0

64-Bit Float
2560.00390625
20.0
7680.0078125
40.0
--------------------------------------------------------------

hier der Link zur Beschreibung der Assembler Befehle https://hjlebbink.github.io/x86doc/
und zum Vaddpd https://hjlebbink.github.io/x86doc/html/ADDPD.html

Ich steh komplett auf dem Schlauch! Für mein Verständnis mach ich alles richtig!
Auch rumprobieren mit anderen Befehlen bringt nichts, höchstens Abstürze.
Ich habe Windows 10 Ryzen5700 Purebasic V6.0 x32, da sollte nichts im Wege stehen, dass das geht!

Testcode

Code: Alles auswählen

EnableExplicit

; LINK zur Doku der Assembler-Befehle
; https://hjlebbink.github.io/x86doc/
 
Structure TVector3f
  x.f
  y.f
  z.f
EndStructure  

Structure TVector4f Extends TVector3f
   w.f
EndStructure
 
Structure TVector3d
  x.d
  y.d
  z.d  
EndStructure

Structure TVector4d Extends TVector3d
   w.d
EndStructure

Debug "SyzeOf(TVector4d) = " + SizeOf(TVector4d)

Enumeration 
  #MMX_SSE_Off        ; No SSE present
  #MMX_SSE_x32        ; 32-Bit Assembler SSE Code
  #MMX_SSE_x64        ; 64-Bot Assembler SSE Code
  #MMX_SSE_C_Backend  ; For Future use in the C-Backend (maybe it will be possible To force SSE With intrinsic Macros)
EndEnumeration
  
CompilerIf #PB_Compiler_Backend  = #PB_Backend_Asm  ; ***** ASM Backend *****
  ; only in ASM-Backend we can use ASM SSE Commands
  
    CompilerIf  #PB_Compiler_64Bit  ; 64Bit-Compiler  on x64 Processor
      #MMX_USE_SSE = #MMX_SSE_x64
    CompilerElse
       #MMX_USE_SSE = #MMX_SSE_x32  ; 32Bit Compiler on x64 Processor
    CompilerEndIf
          
CompilerElse                                        ; ***** C-BackeEnd  *****
  #MMX_USE_SSE =  #MMX_SSE_C_Backend
  
CompilerEndIf

 Macro mac_Vector4_ADD(OUT, IN1, IN2)
  OUT\x= IN1\x + IN2\x   
  OUT\y= IN1\y + IN2\y   
  OUT\z= IN1\z + IN2\z   
  OUT\w= IN1\w + IN2\w   
EndMacro

Macro mac_Vector4_SUB(OUT, IN1, IN2)
  OUT\x= IN1\x - IN2\x   
  OUT\y= IN1\y - IN2\y   
  OUT\z= IN1\z - IN2\z   
  OUT\w= IN1\w - IN2\w     
EndMacro

Macro mac_Vector4_MUL(OUT, IN1, IN2)
  OUT\x= IN1\x * IN2\x   
  OUT\y= IN1\y * IN2\y   
  OUT\z= IN1\z * IN2\z   
  OUT\w= IN1\w * IN2\w     
EndMacro

Macro mac_Vector4_DIV(OUT, IN1, IN2)
  OUT\x= IN1\x / IN2\x   
  OUT\y= IN1\y / IN2\y   
  OUT\z= IN1\z / IN2\z   
  OUT\w= IN1\w / IN2\w     
EndMacro

Macro mac_Vector4_Scale(OUT, IN, Factor)
  OUT\x= IN\x * Factor   
  OUT\y= IN\y * Factor   
  OUT\z= IN\z * Factor    
  OUT\w= IN\w * Factor     
EndMacro

Procedure.s Get_MMX_USE_SSE_String()
  Protected ret.s
  
  Select #MMX_USE_SSE
      
    Case #MMX_SSE_Off
      ret = "MMX_SSE_OFF"
      
    Case #MMX_SSE_x32
      ret = "MMX_SSE_x32_ASM"
      
    Case #MMX_SSE_x64
       ret = "MMX_SSE_x64_ASM"
     
    Case #MMX_SSE_C_Backend
       ret = "MMX_SSE_C_BackEnd"
      
   EndSelect
   ProcedureReturn ret    
EndProcedure

Debug "Compiled Type : " + Get_MMX_USE_SSE_String()
  
 Procedure Vector4f_Add(*OUT.TVector4f, *IN1.TVector4f, *IN2.TVector4f) 
      
  CompilerSelect #MMX_USE_SSE
    
    CompilerCase #MMX_SSE_x64   ; 64 Bit-Version
      !Mov rax, [p.p_IN1]       ; Move Data-Pointer 1 to rax
    	!Mov rdx, [p.p_IN2]       ; Move Data-Pointer 2 to rdx
      !Movups  Xmm0, [rax]      ; Move 16 Byte from Data 1 to Xmm0 Register
      !Movups  Xmm1, [rdx]      ; Move 16 Byte from Data 2 to Xmm1 Register
      !Addps   Xmm1, Xmm0       ; Add 4 packed single precision Float
      !Mov rax, [p.p_OUT]       ; Move Data-Pointer of Result to rax
      !Movups  [rax], Xmm1      ; Move the 16-Byte Result to our Result Data
      
    CompilerCase #MMX_SSE_x32   ; 32 Bit Version
      !Mov eax, [p.p_IN1]
    	!Mov edx, [p.p_IN2]
      !Movups  Xmm0, [eax]
      !Movups  Xmm1, [edx]
      !Addps   Xmm1, Xmm0
      !Mov eax, [p.p_OUT]
      !Movups  [eax], Xmm1
      
    CompilerCase #MMX_SSE_C_Backend   ; for the C-Backend
      mac_Vector4_ADD(*OUT, *IN1, *IN2)    
      
    CompilerDefault             ; Classic Version
      mac_Vector4_ADD(*OUT, *IN1, *IN2)        
  CompilerEndSelect
  
EndProcedure

; VectorAdd mit 4xDouble über die AVX 256/512-Bit Befehle
; AVX-Vector Befehle benötigen nun 3 Operanden  Vaddpd [Ergebnis], [Operand1], [Operand2]

Procedure Vector4d_Add(*OUT.TVector4d, *IN1.TVector4d, *IN2.TVector4d) 
      
  CompilerSelect #MMX_USE_SSE
    
    CompilerCase #MMX_SSE_x64     ; 64 Bit-Version
      !Mov rax, [p.p_IN1]         ; Move Data-Pointer 1 to rax
    	!Mov rdx, [p.p_IN2]         ; Move Data-Pointer 2 to rdx
      !Vmovups  Ymm0, [rax]       ; Move 32 Byte unaligned from Data 1 to Ymm0 Register 
      !Vmovups  Ymm1, [rdx]       ; Move 32 Byte unaligned from Data 2 to Ymm1 Register
      !Vaddps   Ymm2, Ymm1, Ymm0  ; Add 4 packed single precision Float
      !Mov rax, [p.p_OUT]         ; Move Data-Pointer of Result to rax
      !Vmovups  [rax], Ymm2       ; Move the 32-Byte unaligned our Result Data
      
    CompilerCase #MMX_SSE_x32   ; 32 Bit Version
      !Mov eax, [p.p_IN1]
    	!Mov edx, [p.p_IN2]
    	!Vmovupd  Ymm0, [eax]       ; VectorMoveUnalignedPackeDouble
      !Vmovupd  Ymm1, [edx]
      !Vaddpd   Ymm2, Ymm1, Ymm0  ; VectorAddPackedDouble
      !Mov eax, [p.p_OUT]
      !Vmovupd  [eax], Ymm2
      
    CompilerCase #MMX_SSE_C_Backend   ; for the C-Backend
      mac_Vector4_ADD(*OUT, *IN1, *IN2)        
      ; __m256d c = _mm256_add_pd(a, b ; 
      
    CompilerDefault             ; Classic Version
      mac_Vector4_ADD(*OUT, *IN1, *IN2)        
  CompilerEndSelect
  
EndProcedure
  
  
  Define.i t1, t2, I, J
  
  ; ----------------------------------------------------------------------
  ;  TestCode Single Float
  ; ----------------------------------------------------------------------
  
  Debug ""
  Debug "32-Bit Float"
 
  Define.TVector4f A, B, C
  
  A\x = 1
  A\y = 2
  A\z = 3
  A\w = 4
  
  B\x = 10
  B\y = 20
  B\z = 30
  B\w = 40
  
  
  #cstRounds = 0 ; 0000000
  
  t1 = ElapsedMilliseconds()
  For I = 0 To #cstRounds
    Vector4f_Add(C, A, B)
  Next
  t1 = ElapsedMilliseconds() - t1
  
  Debug C\x
  Debug C\y
  Debug C\z
  Debug C\w
    
  ; ----------------------------------------------------------------------
  ;  TestCode Double Float
  ; ----------------------------------------------------------------------
  
  Debug ""
  Debug "64-Bit Float"

  ; Define.TVector4f dA, dB, dC  ; das war der Fehler! Das muss natürlich Double sein
  
  Define.TVector4d dA, dB, dC
   
  dA\x = 1
  dA\y = 2
  dA\z = 3
  dA\w = 4
  
  dB\x = 10
  dB\y = 20
  dB\z = 30
  dB\w = 40
  
  t1 = ElapsedMilliseconds()
  For I = 0 To #cstRounds
    Vector4d_Add(dC, dA, dB)
  Next
  t1 = ElapsedMilliseconds() - t1
  
  Debug dC\x
  Debug dC\y
  Debug dC\z
  Debug dC\w
  
  MessageRequester("Execution times", "Vector4f   : = " + t1 + #CRLF$ + "Vector4d : = " + T2)

Zuletzt geändert von SMaag am 06.12.2022 17:38, insgesamt 1-mal geändert.
SMaag
Beiträge: 150
Registriert: 08.05.2022 12:58

Re: SSE Beschleunigung

Beitrag von SMaag »

Ok Tomaten auf den Augen, kaum gepostet hab ich Fehler gesehen!

es muss beim Testcode für Double auch Double definiert werden statt single
Define.TVector4d dA, dB, dC statt Define.TVector4f dA, dB, dC

Jetzt geht es!
Zuletzt geändert von SMaag am 06.12.2022 17:51, insgesamt 1-mal geändert.
Antworten