![]() |
[ASM / SSE] Vektoroperationen
Nachmittag DPLer,
ich habe mich heute mal daran Gesetz, die SIMD-Extensions ein wenig genauer unter die Lupe zu nehmen, speziell wie man diese zur Berechnung von Vektoren einsetzen kann. Ich hab dafür ein paar kleine Testroutinen gebastelt, und hätte 2 Fragen: 1. Wo liegt noch Optimierungspotential? 2. Warum ist die normale Addition gegenüber der SSE-Single-Vector Addition nur minimal schneller? (Messwerte für 2 Millionen Verticies operationen, einmal addieren je 2er Vektoren, 1x Multiplizieren: Ganz normaler Delphi-Code: im Schnitt 109.000 Zykel SSE-Single-Vector: im Schnitt 102.000 Zykel Addieren ganzer Arrays: 62.736). Zum Quelltext dahinter: Die Vektoren sind als einfache Records spezifiziert
Delphi-Quellcode:
const
ALENGTH = 2000000; type TTestVec = packed record x, y, z, w: Single; end; ... Vecs1: array[0..ALENGTH] of TTestVec; Vecs2: array[0..ALENGTH] of TTestVec; ResVecs: array[0..ALENGTH] of TTestVec; Und die Methoden(hier mal nur die zum Addieren, die zum Multiplizieren dementsprechend statt + / ADDPS mit * / MULPS) Standard Delphi:
Delphi-Quellcode:
Single-Vector-SSE:
function AddVecs(const av1, av2: TTestVec): TTestVec;
begin Result.x := av1.x + av2.x; Result.y := av1.y + av2.y; Result.z := av1.z + av2.z; Result.w := av1.w + av2.w; end;
Delphi-Quellcode:
und schließlich Array-SSE-Addieren:
function AddVecsSSE(const av1, av2: TTestVec): TTestVec;
var p1, p2: Pointer; begin p1 := @av1.x; p2 := @av2.x; asm MOV ECX, p1 MOV EDX, p2 MOVUPS XMM0, [ECX] MOVUPS XMM1, [EDX] ADDPS XMM0, XMM1 MOV ECX, @Result.x MOVUPS [ECX], XMM0 end; end;
Delphi-Quellcode:
procedure AddVecsArraySSE(const av1, av2: Pointer; const outarray: Pointer; const Length: Integer; const Strafing: Integer);
begin asm //ECX = 1st Array //EDX = 2nd Array //EBX = Length of the Array //EAX = Pointer to the outarray MOV ECX, av1 MOV EDX, av2 MOV EBX, Length MOV EAX, outarray @@LoopLabel: MOVUPS XMM0, [ECX] MOVUPS XMM1, [EDX] ADDPS XMM0, XMM1 MOVUPS [EAX], XMM0 //Die Pointer um Strafing verschieben ADD ECX, Strafing ADD EDX, Strafing ADD EAX, Strafing DEC EBX //Sind wir mit der Länbe bei -1, haben wir unser Array durch CMP EBX, -1 JNE @@LoopLabel end; end; Und Aufgerufen wird der Spass mit:
Delphi-Quellcode:
Vielen Dank schonmal,
for x := 0 to ALENGTH do
begin ResVecs[x] := AddVecs(Vecs1[x], Vecs2[x]); end; for x := 0 to ALENGTH do begin ResVecs[x] := AddVecsSSE(Vecs1[x], Vecs2[x]); end; AddVecsArraySSE(@Vecs1[0].x, @Vecs2[0].x, @ResVecs[0].x, ALENGTH, 16); Edlmann P.S. Ist nur ne Testimplementation, um zu schauen wie groß der Performancegewinn ist. |
AW: [ASM / SSE] Vektoroperationen
Delphi-Quellcode:
Kompilieren läßt sich zwar auch Folgendes, nur funktionieren tut es nicht.
function AddVecsSSE(const av1, av2: TTestVec): TTestVec;
{$IF SizeOf(Pointer) = 4} // {$IFDEF Win32} asm MOVUPS XMM0, DQWORD PTR [EAX] // MOVUPS XMM0, &av1 MOVUPS XMM1, DQWORD PTR [EDX] // MOVUPS XMM1, &av2 ADDPS XMM0, XMM1 MOVUPS DQWORD PTR [ECX], XMM0 // MOVUPS &Result, XMM0 end; {$ELSE} begin Result.x := av1.x + av2.x; Result.y := av1.y + av2.y; Result.z := av1.z + av2.z; Result.w := av1.w + av2.w; end; {$IFEND}
Delphi-Quellcode:
Schade auch, daß SSE irgenwie kein Variablen mag.
asm
MOVUPS XMM0, DQWORD PTR [EAX] // MOVUPS XMM0, &av1 ADDPS XMM0, DQWORD PTR [EDX] // ADDPS XMM0, &av2 MOVUPS DQWORD PTR [ECX], XMM0 // MOVUPS &Result, XMM0 end; [edit] Doch, mag es.
Delphi-Quellcode:
Aber bei 32 Bit brauchte ich nie [ ], obwohl, ich bin mir grade nicht sicher, ob damals die Parameter nie ByReference übergeben wurden.
{$IF SizeOf(Pointer) = 4} // {$IFDEF Win32}
asm MOVUPS XMM0, DQWORD PTR [&av1] MOVUPS XMM1, DQWORD PTR [&av2] ADDPS XMM0, XMM1 MOVUPS DQWORD PTR [&Result], XMM0 end; {$ELSE} |
AW: [ASM / SSE] Vektoroperationen
Okay, das läuft schonmal etwa 10% schneller...
Nur warum liegt av1 bei DQWORD PTR [EAX], av2 bei EDX und Result bei ECX? Muss man die nicht eigentlich erst dort hin moven? |
AW: [ASM / SSE] Vektoroperationen
Eigentlich liegt Result in EAX, aber nur, wenn es klein genug ist und wenn es sich nicht um Typen mit Compilermagic (automatischer Speicherverwaltung) handelt.
Alles andere wird als Var/Out-Parameter übergeben. Also alles mit maximal 32 Bit (eventuell auch 64 Bit ... bin mir grade nicht sicher, aber bei den In-Parametern sind Int64 und Double ein bissl anders) und wenn es kein String, dyn. Array, Interface oder Variant ist. Die interne Signatur sieht also so aus:
Delphi-Quellcode:
procedure AddVecsSSE(const av1, av2: TTestVec; var Result: TTestVec);
[add] Result in EAX und EDX = 64 Bit :angle: |
AW: [ASM / SSE] Vektoroperationen
Ach klar, macht Sinn...Danke, werd mal versuchen die Schleife für Arrays ähnlich zu optimieren
|
AW: [ASM / SSE] Vektoroperationen
Zitat:
|
AW: [ASM / SSE] Vektoroperationen
Zitat:
|
AW: [ASM / SSE] Vektoroperationen
Delphi-Quellcode:
Keine Ahnung, ob's richtig ist.
type
TTestVec = packed record x, y, z, w: Single; end; TTestVecArr = array[0..0] of TTestVec; PTestVecArr = ^TTestVecArr; procedure AddVecsArraySSE(av1, av2: Pointer; outarray: Pointer; Count: Integer); {$IF SizeOf(Pointer) = 4} // {$IFDEF Win32} //asm // MOV EDI, ECX // MOV ECX, [EBP+8] // DEC ECX // CMP ECX, 0 // JL @@exit // @@loop: // MOVUPS XMM0, DQWORD PTR [EAX + ECX * 16] // MOVUPS XMM1, DQWORD PTR [EDX + ECX * 16] // ADDPS XMM0, XMM1 // MOVUPS DQWORD PTR [EDI + ECX * 16], XMM0 // DEC ECX // ZGE @@loop // @@exit: //end; asm MOV EDI, ECX MOV ECX, [EBP+8] CMP ECX, 0 JLE @@exit DEC ECX CMP ECX, $07FFFFFF JG @@exit SHL ECX, 4 @@loop: MOVUPS XMM0, DQWORD PTR [EAX + ECX] MOVUPS XMM1, DQWORD PTR [EDX + ECX] ADDPS XMM0, XMM1 MOVUPS DQWORD PTR [EDI + ECX], XMM0 SUB ECX, 16 JNZ @@loop @@exit: end; {$ELSE} //type // TSingleArr = array[0..0] of Single; // PSingleArr = TSingleArr; //var // i: Integer; //begin // for i := (Count * 4) - 1 downto 0 do // PSingleArr(outarray)[i] := PSingleArr(av1)[i] + PSingleArr(av2)[i]; //end; var i: Integer; begin for i := Count-1 downto 0 do begin PTestVecArr(outarray)[i].x := PTestVecArr(av1)[i].x + PTestVecArr(av2)[i].x; PTestVecArr(outarray)[i].y := PTestVecArr(av1)[i].y + PTestVecArr(av2)[i].y; PTestVecArr(outarray)[i].z := PTestVecArr(av1)[i].z + PTestVecArr(av2)[i].z; PTestVecArr(outarray)[i].w := PTestVecArr(av1)[i].w + PTestVecArr(av2)[i].w; end; end; {$IFEND} Und Schade, daß *16 scheinbar vergessen wurde zu implementieren. *2, *4 und *8 gibt es ja auch und daß bei der Größe der MMX-Register :cry: ![]() [edit] Code siehe: Zitat-Funktion ... die Funktion des Delphi-Tags ist immernoch einfach nur schrecklich |
AW: [ASM / SSE] Vektoroperationen
Die Variante läuft auf jeden Fall wunderbar, und der Performancegewinn kann sich auch sehen lassen.
Ich blick noch nicht so 100% durch, aber *fängt an kaffee in Verstehen zu konvertieren* ;) [Edit] Okey, habs soweit gecheckt...Allerdings geht so ja die Möglichkeit verschiedener Offsets verloren - bzw wird auf power of 2 beschränkt...aber Reicht ja. Vielen Dank mal wieder himitsu ;) |
AW: [ASM / SSE] Vektoroperationen
Ich glaub ich hab einen Bufferoverrun.
Es wird von
Delphi-Quellcode:
gerechnet und nicht von
0 bis Count
Delphi-Quellcode:
.
0 bis Count-1
Hab daher oben noch ein paar
Delphi-Quellcode:
verbaut.
DEC ECX
Eigentlich macht man solche Array-Operationen über ESI (Source), EDI (Destination) und ECX (Counter). z.B. wenn du dir Funktionen wie Copy/Move oder Pos ansiehst. Und eigentlich gibt es spezielle Registerzugriffe, damit man mit nur einem Zähler als Offset für beide/alle Arrays auskommt. Wobei einige Register spezielle Sonderfunktionen besitzen und oder welche sich für bestimmte Dinge bei Vielen eingebürgert haben. Eben EBX als Zwischenspeicher für den Stack ECX für Zähler oder ESI und EDI für Arrays
Code:
MOV ESI, &Quelle // Byte-Arrays
MOV EDI, &Ziel MOV ECX, &Anzahl @1: MOV AL, [ESI] MOV [EDI], AL INC ESI INC EDI DEC ECX JNZ @1
Code:
MOV ESI, &Quelle
MOV EDI, &Ziel MOV ECX, &Anzahl @1: MOV AL, [ESI] MOV [EDI], AL INC ESI INC EDI LOOP @1
Code:
LOOP selber ist aber nicht unbedingt ein schneller Befehl (keine Ahnung warum),
MOV ESI, &Quelle
MOV EDI, &Ziel MOV ECX, &Anzahl @1: MOV AL, [ESI + ECX] MOV [EDI + ECX], AL LOOP @1 da ist
Code:
und
DEC ECX
Code:
dann doch etwas besser.
JNZ @1
Aber LOOP zeigt die Spezialisierung von Registern sehr gut. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:03 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz