Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi [ASM / SSE] Vektoroperationen (https://www.delphipraxis.net/168841-%5Basm-sse%5D-vektoroperationen.html)

Edlmann 13. Jun 2012 17:37


[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:
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;
Single-Vector-SSE:
Delphi-Quellcode:
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;
und schließlich Array-SSE-Addieren:
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:
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);
Vielen Dank schonmal,
Edlmann

P.S. Ist nur ne Testimplementation, um zu schauen wie groß der Performancegewinn ist.

himitsu 13. Jun 2012 19:12

AW: [ASM / SSE] Vektoroperationen
 
Delphi-Quellcode:
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}
Kompilieren läßt sich zwar auch Folgendes, nur funktionieren tut es nicht.
Delphi-Quellcode:
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;
Schade auch, daß SSE irgenwie kein Variablen mag.

[edit]
Doch, mag es.
Delphi-Quellcode:
{$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}
Aber bei 32 Bit brauchte ich nie [ ], obwohl, ich bin mir grade nicht sicher, ob damals die Parameter nie ByReference übergeben wurden.

Edlmann 13. Jun 2012 19:18

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?

himitsu 13. Jun 2012 19:46

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:

Edlmann 13. Jun 2012 19:51

AW: [ASM / SSE] Vektoroperationen
 
Ach klar, macht Sinn...Danke, werd mal versuchen die Schleife für Arrays ähnlich zu optimieren

himitsu 13. Jun 2012 20:19

AW: [ASM / SSE] Vektoroperationen
 
Zitat:

Delphi-Quellcode:
MOV EBX, Length

EBX solltest du nicht einfach so überschreiben, ohne dessen Wert zu speichern und hinterher wiederherzustellen. :warn:

Edlmann 13. Jun 2012 20:54

AW: [ASM / SSE] Vektoroperationen
 
Zitat:

Zitat von himitsu (Beitrag 1170735)
Zitat:

Delphi-Quellcode:
MOV EBX, Length

EBX solltest du nicht einfach so überschreiben, ohne dessen Wert zu speichern und hinterher wiederherzustellen. :warn:

Ist mir auch schon aufgefallen, hab noch nicht so viel mit asm gemacht und dementsprechend vergessen gehabt...ist gefixxt der Fehler ;)

himitsu 13. Jun 2012 20:58

AW: [ASM / SSE] Vektoroperationen
 
Delphi-Quellcode:
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}
Keine Ahnung, ob's richtig ist.
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:


Delphi-Referenz durchsuchenLength ist ein unpraktischer Name.


[edit]
Code siehe: Zitat-Funktion ... die Funktion des Delphi-Tags ist immernoch einfach nur schrecklich

Edlmann 13. Jun 2012 21:07

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 ;)

himitsu 13. Jun 2012 21:23

AW: [ASM / SSE] Vektoroperationen
 
Ich glaub ich hab einen Bufferoverrun.
Es wird von
Delphi-Quellcode:
0 bis Count
gerechnet und nicht von
Delphi-Quellcode:
0 bis Count-1
.
Hab daher oben noch ein paar
Delphi-Quellcode:
DEC ECX
verbaut.



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:
MOV ESI, &Quelle
MOV EDI, &Ziel
MOV ECX, &Anzahl
@1:
MOV AL, [ESI + ECX]
MOV [EDI + ECX], AL
LOOP @1
LOOP selber ist aber nicht unbedingt ein schneller Befehl (keine Ahnung warum),
da ist
Code:
DEC ECX
und
Code:
JNZ @1
dann doch etwas besser.
Aber LOOP zeigt die Spezialisierung von Registern sehr gut.

Edlmann 13. Jun 2012 21:28

AW: [ASM / SSE] Vektoroperationen
 
Hört sich erstmal ziemlich kontraproduktiv an dass ein Befehl speziell für Schleifen langsamer ist als ein Jump-Befehl zum selben Label...

Aber naja, wieder was für guten Programmierstil gelernt.

himitsu 13. Jun 2012 21:30

AW: [ASM / SSE] Vektoroperationen
 
Es gab mal dazu ein paar Thread (mit/von mir).
Dachte ja auch ein LOOP sei besser, als zwei DEC+JNZ :oops:


Ich hoffe du hast meinen Nachtrag gesehn.
Zitat:

Ich glaub ich hab einen Bufferoverrun.
Es wird von
Delphi-Quellcode:
0 bis Count
gerechnet und nicht von
Delphi-Quellcode:
0 bis Count-1
.
Hab daher oben noch ein paar
Delphi-Quellcode:
DEC ECX
verbaut.

Edlmann 13. Jun 2012 21:36

AW: [ASM / SSE] Vektoroperationen
 
Jupp, hab ich... aber müsste es nicht statt

Delphi-Quellcode:
  CMP ECX, 0
  JLE @@exit
  DEC ECX
  CMP ECX, $07FFFFFF
  JG @@exit
Erst nach dem 2. Compare verringert werden?

Delphi-Quellcode:
  CMP ECX, 0
  JLE @@exit
  CMP ECX, $07FFFFFF
  JG @@exit
  DEC ECX
Oder macht das an dieser Stelle keinen Unterschied?

himitsu 13. Jun 2012 21:46

AW: [ASM / SSE] Vektoroperationen
 
Ist nahezu egal und effektiv macht es keinen Unterschied, da ein Array eh nie so groß werden kann. (vorallem Dank der Speicherdefragmentierung ... überall liegen DLLs und anderes Zeugs im Weg)

Das letzte CMP soll nur absichern, daß kein Integerüberlauf auftritt, wenn man für Count was zu Großes übergibt, da dort ja mal 16 (shl 4) gerechnet wird.

Edlmann 13. Jun 2012 21:53

AW: [ASM / SSE] Vektoroperationen
 
Okey...ach klar, ist ja JG. Werds trotzdem mal hinter das 2. CMP packen, der übersicht halber.

Edlmann 14. Jun 2012 10:04

AW: [ASM / SSE] Vektoroperationen
 
Bin jetzt grad dabei die SSE-Beschleunigung in meine Vektor-Unit einzubauen...

allerdings steh ich grad vor nem Problem: Ich möchte die Länge eines Vektors bestimmen...
Die einzelnen Werte zu quadrieren ist ja kein Problem, allerdings scheiter ich grad dabei
eine Alternative zum C++ Intrinsic _mm_hadd_ps zu finden...Dieser Intrinsic addiert alle 4 Werte
in einem MMX Register und speichert das Ergebnis im Ersten Wert des Ergebnis-Registers...
Man könnte natürlich - angenommen man hat die quadrierten Werte in MMX0 - in MMX1 die Werte mit SHUFPS um 1 verschoben speichern (x = MMX0.y, y = MMX.z etc), in MMX2 dann um 2 verschoben und in MMX3 um3 verschoben,
diese 4 Register dann addieren, und man hätte das Ergebnis...Doch die Performance davon ist definitiv
nicht besser als der Native Quelltext ;)

Hat da jeman deine Idee?

Delphi-Quellcode:
//Deklaration des Vectors
TVec4 = record
   x, y, z, w: Single;
end;

//GetLength prozedur
function GetLength(vector4: TVec4): Single;
asm
  MOVUPS XMM0, DWORD PTR [&vector4]
  MULPS XMM0, XMM0
  MOVUPS DQWORD PTR [&Result], XMM0
  //Hier müssten jetzt die Werte aus XMM0 alle aufaddiert und wieder in MMX0 gespeichert werden
  SQRTSS XMM0, XMM1
  MOVSS XMM0, Result
end;

Desmulator 14. Jun 2012 10:27

AW: [ASM / SSE] Vektoroperationen
 
Zitat:

Zitat von himitsu (Beitrag 1170746)
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

Jetzt hast du aber was verwechselt. Wir machen das mit den Registern jetzt nochmal ganz von vorne.
Es gibt bei x86-kompatiblen Prozessoren 8 große Register, übernommen vom 8086, damals noch 16-bit.
Vier dieser 8 Register lassen sich unterteilen in den hohen und tiefen Teil, woduch erneut 8 8-Bit Register dargestellt werden.
Jedes dieser Register hat einen speziellen, seine Funktion zugehörigen Name.
Der hohe Teil wird immer durch ein h-Suffix und der tiefe Teil durch ein l-Suffix gekennzeichnet. Beide Teile zusammen bilden
dann das 16-Bit Register, dass x als Suffix besitzt.
  • ax[ah:al]: Akkumulator, damals als Zielregister für die meisten Rechenoperationen.
  • bx[bh:bl]: Base, wurde früher von BIOS-Routinen genutzt um zusammen mit dem es-Segment ein Offset, eine Basis, zubilden,
    an der Daten von der Festplatte in den Speicher geladen wurden. ( hat nichts mit dem Stack zu tun )
  • cx[ch:cl]: Counter, für loop-, jcxnz- und rep-Instruktionen.
  • dx[dh:dl]: Data, fürs Speichern von anderen Werten, das Allzeckregister. Wird auch von mul und div genutzt.
Die folgenden Register können nicht unterteilt werden und haben ganz spezielle Funktionen.
  • di : Destination Index, Zeiger für String-Operationen.
  • si : Source Index, Zeiger für String-Operationen.
  • sp : Stack Pointer, Zeiger für den Stack, wird bei jedem push vekleinert, bei jedem pop vergrößert.
  • bp : Base Pointer. Das ist nun wirklich mit das wichtigste Register überhaupt. Wenn eine Funktion ihrer Parameter über den Stack übergeben bekommt, dann kann sie,
    wenn sie bp = sp setzt, alle Parameter ab dem Offset [ebp+0x08] ( in 32-bit ) erreichen, und Platz für ihre internen Variablen kann sie via sub esp, size reservieren.
    Diese sind dann ab [ebp-0x04] erreichbar. Cool oder? :)
Aber das sind doch alles nur 16-bit Register, wir arbeiten doch mit 32-bit? Richtig, darum packt man auch im Protected Mode des x86 einfach ein e vor das Register, wie extended
und man erhält die bekannten Register eax, ebx, ecx, edx, edi, esi, esp, ebp. Die oberen 16-Bit der Register sind übrigens nicht direkt adressierbar.

Welche Register darf eine Funktion nun benutzen? Alle. Allerdings nicht einfach so. Es gibt die sogenannten GPR ( General Purpose Register ), namentlich genannt sind das
eax, ecx und edx, diese darf eine Funktion frei verändern und muss nicht garantieren, dass sie nach dem Funktionsaufruf ihren Wert behalten. Die anderen Register müssen dagegen erst vor dem Gebrauch geschützt werden.

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
Dieser Code ist so eher uncool. Wenn man die Register und ihre Funktionen wirklich nutzen möchte, dann sollte man mit die Stringfunktionen nutzen ( movs, stos, lods, cmps usw. )
um die Register EDI und ESI einzubrigen und das rep-Prefix mit all seinen Variationen. ( rep, repne, repnz, .. )
Code:
mov edi, Ziel
mov esi, Quelle
mov ecx, Anzahl
rep movsb
Fertig. Jetzt schauen wir uns das mal an.
Code:
rep movsb ; 3 + Anzahl Clocks, 2 Byte
benötigt ( 3 + Anzahl ) Clocks um fertig zu werden. Die zu dekodierenden Bytes sind zwei. Einmalig.
Dagegen den anderen Code.
Code:
MOV AL, [ESI] ; 1 Clock, 2 Byte
MOV [EDI], AL ; 1 Clock, 2 Byte
INC ESI      ; 1 Clock, 1 Byte
INC EDI      ; 1 Clock, 1 Byte
DEC ECX      ; 1 Clock, 1 Byte
JNZ @1        ; 1 Clock, 2 Byte
Ein einziges Byte zu kopieren benötigt also nun 6 Clocks und 9 Bytes die dekodiert werden müssen.
Bedeutet also im Vergleich:

Mit rep movsb:
Clocks: 3 + Anzahl, Bytes: 2
Ohne rep movsb:
Clocks: 6 * Anzahl, Bytes: 9 * Anzahl
Man muss kein Profi sein um zu erkennen, dass das rein haut.

Den Unterschied nennt man CISC und RISC.
rep mosvb ist ein Befehl wie man ihn eher bei den Complex Instruction Sets finden würde.
Deine Variante ist die eines RISC, der diese allerdings wesentlich schneller ausführen kann, da seine Pipeline nicht derart komplex gestaltet ist.

Und was ist der heute Prozessor für einer? Schaut man sich die Adressierungsmöglichkeiten usw. an, ist er ganz klar ein CISC. Auch mit den Erweiterungen wie SSE, 3D!Now und das ganze Zeug macht er einen weiten Schritt auf das Modell des Complex Instruction Set. Dazu gehört auch die loop-Instruktion. Sie übernimmt mehrer Vorgänge auf einmal.
Das war früher praktisch, da die Prozessoren langsamer waren und so ein Befehl Geschwindigkeit brachte.

Zitat:

Zitat von himitsu (Beitrag 1170746)
LOOP selber ist aber nicht unbedingt ein schneller Befehl (keine Ahnung warum),
da ist
Code:
DEC ECX
und
Code:
JNZ @1
dann doch etwas besser.
Aber LOOP zeigt die Spezialisierung von Registern sehr gut.

Warum ist loop nun langsamer? Die heutigen Prozessoren tendieren wieder zum RISC, also dem Reduced Instruction Set. Sie können einfach Befehle extrem schnell ausführen. Intel und AMD arbeiten stark an ihrern Pipelines um auch die letzte Picosekunde raus zu kitzeln. loop ist ein Artefakt aus den alten Zeiten und wird nicht mehr wirklich gepflegt. Mit so einem Prozessor ist es wie mit eine API die sich in Entwicklung befindet. Alte Funktionen bleiben erhalten, werden aber nicht mehr verbessert oder angepasst, weil man ein neues Modell verfolgt.

Der Vollständigkeit halber:
Code:
MOV ESI, &Quelle
MOV EDI, &Ziel
MOV ECX, &Anzahl
@1:
MOV AL, [ESI + ECX] ; 1 Clock, 3 Byte
MOV [EDI + ECX], AL ; 1 Clock, 3 Byte
LOOP @1             ; wenn ECX = 0, 2 Byte
                    ;   5 Clock sonst 6 Clock
Clocks: ( 7 * Anzahl ) + 1, Bytes: 8 * Anzahl

Jetzt wundert man sich warum SSE nicht wirklich schneller ist als die normalen Funktionen.
SSE wurde 1999 entwickelt. Natürlich auch verbessert und vieles mehr. Doch kaum einer nutzt es. Das weiß auch Intel und AMD. Diese setzen also nun mehr darauf die "alltäglichen" Instruktionen zu verschnellern als solche speziellen. Es muss ja auch Medienwirksam sein. Wen interessiert ein Benchmarktest von SSE 3.0? Niemanden wirklich. Wenn es aber heißt, Befehle werden 150% schneller ausgeführt und man ein kleines Programm mit den üblichen Befehlen sieht ( mov, cmp, test, shl, shr usw. ) dann wird man hellhörig.
Wie der Threadersteller außerdem selbst sicherlich festgestellt hat ist SSE garnicht mal so leicht zu lernen und zu verstehen. Informationen sind nur sperrlich vorhanden und wirklich nützliche Funktionen sind auch nicht dabei. Das Kreuzprodukt ist immer noch ein riesiger Klotz. Lassen viele Firmen die Finger davon. Dazu kommt noch, dass man sich fast nie sicher sein kann, dass jeder Prozessor die entsprechende SSE-Version unterstüzt und bevor man 5 Versionen des selben Programms schreibt, nur um SSE best möglich zu verwenden und das letzte Quäntchen aus der Rechenleistung herraus zukitzeln, wartet man lieber bis Intel oder AMD die normalen Befehle beschleunigen, wo man sich sicher sein kann, das jeder x86 die versteht. Isso.

Die Prozessorhersteller lassen sich auch immer wieder irgendwas neues einfallen, irgendwelche speziellen Instruktionen, die richtig angewand, durch aus ordentlich geschwindigkeit bringen können. Aber kaum ein Mensch hat Lust sich in dem ganzen Wust an Befehlen, die besten für sein Problem herraus zu suchen. Beispiel-Bild.

Der Grund warum es nicht der burner ist, sind die Umstände. Und die richtige Anwendung.

SSE steht für Streaming SIMD Extensions. Und SIMD nochmal genau für Single Instruction, Multiple Data.
Der Zweck von SSE ist möglichst viele Daten gleichzeitig zu verarbeiten.
Dumm nur, dass die meiste Zeit mit dem Laden von Werten aus Register oder Speicher verbraten wird.
Da bringt auch die Parallelisierung von vier Gleitkommazahlen nichts. Die Grafikkarte berechnet mit 18000 Pixelshadern gleichzeit deutlich mehr. ;-)


Das ist das nächste Problem von SSE und MMX usw. Sie wurden auf den Prozessor oben aufgesetzt. Sie haben ewig lange Instruktionen. 4-Byte und mehr. Das braucht alles bis es decodiert ist. Dann noch die entsprechenden Daten laden. Die Pipeline wechseln usw. Netto kommt da nicht viel Zeit bei rum.

Seien es Vektoren oder wie bei MMX Ganzzahlen ( dieses wurde wie der Name ( Multi Media Extension ) sagt, für Farben und Sättigungsrechnung ausgelegt. )
Dabei reden wir aber jetzt nicht von 5 Vektoren sondern von 1310720 Pixeln oder doppelt so vielen Vertices die berechnet werden wollen.
Jetzt wird einer sagen, wieso, dafür ist doch die Grafikkarte zuständig. Ja. Ist sie auch.

Und darum ist SSE auch so sehr mit Vorsicht zu genießen. Wir haben einen Prozessor der einfache Berechnungen ausführen soll, hauptsächlich aber die Maschine steuert und einzellnen Modulen, wie der Grafikkarte, die explizit für ihre Funktion ausgelegt sind, Anweisungen gibt.


SSE ist der Versuch einen allzweck-Prozessor zu bauen. Hat nicht geklappt. CISC ist nicht das ware, da es immer aufgeblähter werden muss um mit zu halten, weil es immer nur ganz spezielle Befehle gibt für spezielle Dinge. RISC ist wieder im kommen. SSE ist aber nicht RISC.

Zitat:

CPUs mit CISC-Befehlssatz waren lange Zeit mikroprogrammiert. Heute findet man kaum noch mikroprogrammierte CISC-CPUs. Ab dem Pentium Pro verfügen die Intel-Prozessoren über eine vorgeschaltete Funktionseinheit, die die komplexen Befehle in RISC-Befehle übersetzt. Je nach Hersteller und CPU werden diese Einheiten ROP, Micro-Op oder µOp genannt. Weitere Beispiele für CPUs mit CISC-Befehlssatz sind der Intel 8086, der Intel 80386, der Motorola 68000, der Zilog Z80 und die CPUs der System z-Reihe von IBM.
Der Beitrag über SSE ist noch lange nicht fertig und ich könnte noch ewig schreiben, aber langsam bekomme ich hunger. Bau dein SSE ein, es ist wirklich cool, aber erhoffe dir jetzt nicht zu viel. SSE ist nicht ausgereift. Ahja nächster Punkt. SSE wurde erfunden um was neues zu schaffen, wir verkaufen Prozessoren mit SSE. Hört sich super an, kein Endanwender weiß, was es ist, kein Programm nutzt es, da neue Technik immer neue Programm erfordert nicht alle Computer direkt SSE besitzen, aber man kauft es trotzdem. Intel und AMD haben was sie wollen. Geld und eine verkümmerter Versuch was gutes zu machen.

Davon mal abgesehen, du hast bei deinem Beispiel 9000 Zyklen weniger. Was erwartest du denn? :-P

Edit: http://www.bernd-leitenberger.de/cisc-risc.shtml super artikel zu CISC und RISC :D

Edlmann 14. Jun 2012 11:05

AW: [ASM / SSE] Vektoroperationen
 
Mittlerweile sind es durch die kleinen aber feinen Optimierungen von Himitsu sogar 20.000 Zykel - und damit bin ich ziemlich zufrieden ;)

Vielen Dank für deine Erklärung zu SSE, hat echt ein paar Fragen geklärt. Allerdings hab ich SSE schon an mehreren Stellen im Einsatz gefunden - an der selben
Stelle an der ich auch im Moment arbeite, 3D-Mathematik, die ja Hauptsächlich in Spielen eingesetzt wird. z.B. die Doom-3-Engine, hat für eigentlich alle damaligen
Befehlssätze einen eigenen Prozessor implementiert (ist natürlich kein Prozessor, hat idSoftware so getauft) - also 3D!Now, MMX, SSE, SSE2, SSE3 und 'AltiVec' (hatte ich vorher noch nie von gehört). Es kann natürlich sein dass der Trend mittlerweile davon abgerückt ist - doom ist ja nicht mehr das neusete, doch es bringt ja noch immer einiges an Performancegewinn (50% bei Arrays)...Und da das ganze heute eigentlich jeder Prozessor unterstützt, und auch auf jedem einen - mehr oder weniger großen - Performancegewinn bringt, werd ich auch jeden Fall teilweise darauf zurückgreifen, um die Zeitkritischsten Sachen zu beschleunigen.

Wird zwar einiges an Zeit dauern bis ich alle operationen so hab wie ich will, aber ists auch für den Lernfaktor wert ;)

Und so extrem unübersichtlich sind auch erweiterte Algorithmen nicht imho...das Kreuzprodukt kann ja berechnet werden durch:

Code:
        __asm
        {
                MOV EAX Op_A                              // Load pointers into CPU regs
                MOV EBX, Op_B

                MOVUPS XMM0, [EAX]                // Move unaligned vectors to SSE regs
                MOVUPS XMM1, [EBX]  
                MOVAPS XMM2, XMM0               // Make a copy of vector A
                MOVAPS XMM3, XMM1               // Make a copy of vector B

                SHUFPS XMM0, XMM0, 0xD8      // 11 01 10 00  Flip the middle elements of A
                SHUFPS XMM1, XMM1, 0xE1       // 11 10 00 01 Flip first two elements of B
                MULPS XMM0, XMM1                 // Multiply the modified register vectors
               
                SHUFPS XMM2, XMM2, 0xE1      // 11 10 00 01 Flip first two elements of the A copy
                SHUFPS XMM3, XMM3, 0xD8     // 11 01 10 00  Flip the middle elements of the B copy
                MULPS XMM2, XMM3                 // Multiply the modified register vectors
             
                SUBPS XMM0, XMM2                  // Subtract the two resulting register vectors
             
                MOVUPS [Ret_Vector], XMM0      // Save the return vector
        }
(Nicht meine Implementierung, an der Delphi Version feil ich noch)

Was zwar nicht wenig ist, aber merklich schneller läuft als der vom Compiler erzeugte Source ;)

Was mich besonders fasziniert ist, dass es immer noch um 5-6000 Zykel schneller ist, einen 4D-Vector per SSE zu berechnen,
als einen 3D-Vector mit normalem Delphi Source...Und das auf einer relativ aktuellen CPU - Welche ja wenn ich das richtig verstanden habe
nicht so wirklich auf SSE setzen / wo SSE nicht so optimiert ist wie es sein könnte. Auf älteren CPU's müsste dann ja das ganze noch mehr Performance einbringen,
oder nicht?

Medium 14. Jun 2012 11:14

AW: [ASM / SSE] Vektoroperationen
 
@Desmulator: :thumb:

Spiele rücken allerdings auch immer mehr von den SIMD ab, und die "Numbercruncher" in Rechenzentren auch zunehmend. Grund dafür ist wohl, dass die Grafikchips immer universeller wurden (was früher noch eine T&L Einheit war, ist heute dank OpenCL und CUDA fast vollwertig programmierbar). Gerade bei Spielen ist ausser der Grafik und Physik (NVidia PhysiX läuft auch auf hauptsächlich der GPU, wenn diese das kann) nicht mehr so arg viele Einsatzstellen gegeben. GPUs geben einfach ZU gute Streaming Vector Prozessoren ab - so gut, dass die Architektur glaube ich mittlerweile auch stark in Clustern genutzt wird. (Siehe NVidia Tesla Serie.)

Wirklich intensiven Gebrauch vom SIMD machen mittlerweile glaube ich nur noch Videode- und Encoder, der Rest dürfte sich als Einzelfälle auffassen lassen. Wo ich aber zustimme: Die zu lernen macht irgendwie trotzdem Spaß :) (Und ein Gewinn ist ja da!)

Edlmann 14. Jun 2012 11:20

AW: [ASM / SSE] Vektoroperationen
 
Da das Projekt an dem ich arbeite kein Tripl-A Titel werden soll, sondern nur ein kleines Spiel mit ner schön anzuschauenden
3D-Grafik, und es mir hauptsächlich um den Lerneffekt geht (hab z.B. schon ne Konsole implementiert, mit ConsoleVar/Command-System, oder den
Flocking Algorithmus von Craig Reynold zur Gegnerbewegung), bringt das hier einiges an Performancegewinn ;) Hab nicht vor mich in PhysX / Cuda einzuarbeiten...
Dafür fühl ich mich in normalem Delphi zu wohl ;D

Desmulator 14. Jun 2012 11:28

AW: [ASM / SSE] Vektoroperationen
 
Persönliche Meinung & Erfahrung
Was ich jetzt sage, kann mir vielleicht den Kopf kosten, aber naja :-P


SSE 3.0 wurde 2004 auf den Markt gebracht. Wenn du also in kauf nimmst, dass ca. 30% der Anwender dein Programm nicht nutzen können, kannst du SSE 3.0 benutzen.
Ansonsten nutze SSE 1.0, das ist verlässlich auf allen Platformen vorhanden. Jedoch fehlen elementare Funktionen.

Schonmal über eine parallelisierte Matrixmultiplikation nachgedacht. Klappt mit SSE eigentlich super, Matrixzeilen spalten, Vektor laden, Vektor 3 mal kopieren, mit jeder Spalte
multiplizieren, ja und dann wirds schwer. SSE erlaubt einem nicht, eine Zeile zusammen zu addieren. addpsh gibts erst bei SSE 3.0 und das nervt.
Welchen Zweck erfüllt mir SSE wenn ich nach dem multiplizieren wieder alles rausladen muss und selbst zusammen addieren darf.
Auch wenn wir von CISC und RISC geredet haben und ich eigentlich mehr der RISC-Fan bin. Wenn man sowas schon wirklich machen muss, solche Erweiterungen, die nun einfach dafür ausgelegt sind, 3D-Anwendungen zu beschleunigen, sollten direkt Befehle wie Kreutzprodukt oder Matrixmultiplikation einbauen, die benötigt werden. Außerdem sollte es batch-Funktionen geben und nicht per Hand jeder einzellne Vektor geladen werden müssen, ähnlich meinem rep movsb beispiel. Wenn muss ich denn mal einen einzellnen Vektor verrechnen? Und das ganze soll dann noch so zeitkritisch sein, dass mit die normale FPU nicht reicht? Also da hört's auf.

Das Ganze könnte doch so aussehen:
Code:
mov edx, &matrix
mov esi, vertexArray
mov ecx, length(vertexArray)
rep mulMatPS
Kein Befehl muss mehr dekodiert werden und in einem Rutsch rechnet er einem alles durch. Das was man auch wirklich braucht. Und dafür gibt's ne Grafikkarte die genau DAS macht.
SSE ist ein Denkfehler mit Designfehler. Der momentane Trend, die Grafikprozessoren mit ihrer Rechenleistung allgemein zugänglicher zu machen ist imo der richtige. Wozu habe ich 1000 Shader auf der Platine wenn ich sie nur für's Bildchen-Malen nutzen kann, obwohl sie wesentlich mehr können?

Zitat:

Was mich besonders fasziniert ist, dass es immer noch um 5-6000 Zykel schneller ist, einen 4D-Vector per SSE zu berechnen,
als einen 3D-Vector mit normalem Delphi Source...Und das auf einer relativ aktuellen CPU - Welche ja wenn ich das richtig verstanden habe
nicht so wirklich auf SSE setzen / wo SSE nicht so optimiert ist wie es sein könnte.
Grund dafür ist die Architektur. Delphi nutzt die FPU. Heißt also einen Werte laden, Rechnen, zurück schreibe, nächsten Wert laden ...
Bei SSE ist der Vorteil eben klar, du lädst direkt alle vier Werte gleichzeitig, die berechnest alle vier gleichzeitig und du schreibst sie auch gleichzeitig wieder zurück.
Bei heutiger Hardware ist es defactor kein Unterschied ob du einen oder vier Werte in den RAM packst. Jede mal neu die Adresse zu laden braucht dagegen mehr Zeit.
Zitat:

Auf älteren CPU's müsste dann ja das ganze noch mehr Performance einbringen,
oder nicht?
SSE wächst natürlich mit. Es werden nicht die gleichen Bausteine wie 1999 verwendet, der Prozessor taktet schneller. Ich denke der Gewinn wird sogar noch geringer sein, da sie viel an der normalen Pipeline geändert hat.

Edlmann 14. Jun 2012 11:33

AW: [ASM / SSE] Vektoroperationen
 
Auf das Problem dass man mit SSE1 noch kein MMX-Register aufaddieren kann bin ich auch gerade gestoßen...ist echt ziemlich nervig...

Man könnte natürlich 2 Varianten schreiben - eine für SSE 3.0 und eine für SSE 1 - dann wäre ich allerdings für diese Prozeduren bei 3 verschiedenen Varianten die implementiert werden müssen...Normal für 64 Bit-Compiler (falls ich mal auf die Idee komme den zu nutzen), SSE1 und SSE3...und wie findet man heraus welches SSE von der CPU unterstützt wird?
Ob SSE generell möglich ist lässt sich ja über das 25.? oder so Bit der CPUInfo abfragen...bzw über System.TestSSE, aber TestSSE unterscheidet nach dokumentation nur zwischen SSE1 und SSE2...

mkinzler 14. Jun 2012 11:35

AW: [ASM / SSE] Vektoroperationen
 
Unter 64Bit ist SSE obligatorisch, da keine FPU-Befehle mehr unterstützt werden.

Edlmann 14. Jun 2012 11:37

AW: [ASM / SSE] Vektoroperationen
 
@mkinzler Soweit klar, deswegen sagte ich ja dass ich auch eine Variante für 64Bit Pflege - welche der Entspricht die komplett ohne assembler auskommt, und deswegen auch für Uralt PC's eingesetzt werden kann (<1999), auch wenn ich kaum glaube das so etwas noch wirklich genutzt wird und wenn, derjenige versucht darauf Spiele zu spielen ;)

[Edit]

Ist ein deklariertes Array eigentlich aligned? bzw kann man ein Array so deklarieren, dass es im Arbeitsspeicher aligned ist? Dann könnte man statt
MOVUPS ja MOVAPS benutzen (aligned Moving), welches einen weiteren Geschwindigkeitsvorteil bringen würde

himitsu 14. Jun 2012 12:07

AW: [ASM / SSE] Vektoroperationen
 
FastMM erstellt und unterteilt seine Speicherblöcke aligned (ich glaub aktuell alle 16 Byte)
dynamische Arrays und alles über GetMem ist somit ordentlich ausgerichtet.

Der Stack ist auch möglist auf 4 Byte ausgerichtet (falls da keiner Mist baut)


Zitat:

und einzellnen Modulen, wie der Grafikkarte, die explizit für ihre Funktion ausgelegt sind,
Wobei man ja seit einer ganzen Weile damit anfängt Code, welcher mal in der CPU berechnet wurde, in die schnellere/bessere GPU auszulagern. (vorallem für Ver/Entschlüsselungen und das Hacken von sowas)
Zitat:

Der momentane Trend, die Grafikprozessoren mit ihrer Rechenleistung allgemein zugänglicher zu machen ist imo der richtige
Wobei eben nicht alles was da gerechnet wird, mit was Graphischem zu tun hat. :angle2:

Edlmann 14. Jun 2012 12:23

AW: [ASM / SSE] Vektoroperationen
 
Aber wenn dynamische Arrays 16 bit Aligned sind müsste doch folgender Code möglich sein:

Delphi-Quellcode:
function AddVecsSSE(const av1, av2: TTestVec): TTestVec;
asm
  MOVAPS XMM0, DQWORD PTR [&av1]
  MOVAPS XMM1, DQWORD PTR [&av2]
  ADDPS XMM0, XMM1
  MOVUPS DQWORD PTR [&Result], XMM0
end;
Er führt allerdings zu einer Zugriffsverletzung...

Desmulator 14. Jun 2012 12:24

AW: [ASM / SSE] Vektoroperationen
 
Zitat:

Zitat von himitsu (Beitrag 1170829)
Zitat:

und einzellnen Modulen, wie der Grafikkarte, die explizit für ihre Funktion ausgelegt sind,
Wobei man ja seit einer ganzen Weile damit anfängt Code, welcher mal in der CPU berechnet wurde, in die schnellere/bessere GPU auszulagern. (vorallem für Ver/Entschlüsselungen und das Hacken von sowas)
Zitat:

Der momentane Trend, die Grafikprozessoren mit ihrer Rechenleistung allgemein zugänglicher zu machen ist imo der richtige
Wobei eben nicht alles was da gerechnet wird, mit was Graphischem zu tun hat. :angle2:

Das sind jetzt Wortspielereien, bloß weil es Grafikkarte heißt. Das "Ding" ist dafür ausgelegt, Datenmassen in kurzer Zeit durch zu schleusen und zu verrechnen, warum also diese Möglichkeit nicht nutzen? Ob der Pixelshader, der nur aus historischen Gründen so heißt, jetzt einen Farbwert berechnet oder ein Byte dekodiert, das ist sowohl dem Pixelshader als auch mit egal. Wenn man es so genau nehmen will, dann sollte der Keyboard Chip von früher auch direkt umbenannt werden müssen, seitdem man damit die 21. Adressleitung frei schaltet zu Keyboard und Memory Size Chip. Für Grafikkarten würde sich das aber cool an hören Massiv Data Streaming Board :-P

Desmulator 14. Jun 2012 12:26

AW: [ASM / SSE] Vektoroperationen
 
Zitat:

Zitat von Edlmann (Beitrag 1170832)
Aber wenn dynamische Arrays 16 bit Aligned sind müsste doch folgender Code möglich sein:

Delphi-Quellcode:
function AddVecsSSE(const av1, av2: TTestVec): TTestVec;
asm
  MOVAPS XMM0, DQWORD PTR [&av1]
  MOVAPS XMM1, DQWORD PTR [&av2]
  ADDPS XMM0, XMM1
  MOVUPS DQWORD PTR [&Result], XMM0
end;
Er führt allerdings zu einer Zugriffsverletzung...

Überprüfs doch einfach, lass dir die Adresse von av1 ausgeben und wenn mod 16 != 0 dann ist es nicht aligned

Edlmann 14. Jun 2012 12:37

AW: [ASM / SSE] Vektoroperationen
 
Scheint 8 bit Aligned zu sein...Addr(Vecs) mod 16 ist immer 8, Egal an welcher Stelle es deklariert wird (zumindest wenn es ein dynamisches Array ist, festes Array ist gar nicht aligned)

Desmulator 14. Jun 2012 12:42

AW: [ASM / SSE] Vektoroperationen
 
Also reserviere dein Array + 15 Byte. Prüfe die Adresse, verschiebe den Pointer um addr mod 16 bytes und du hast einen richtigen Speicherbereich.

Edlmann 14. Jun 2012 12:47

AW: [ASM / SSE] Vektoroperationen
 
Werd ich mal testen, wie hoch da der Geschwindigkeitsgewinn ist...Ist allerdings in der eigentlichen Vektorbibliothek kaum umsetzbar...müsste dafür ein eigenes Array schreiben was immer aligned ist, mal schauen

himitsu 14. Jun 2012 13:53

AW: [ASM / SSE] Vektoroperationen
 
Zitat:

Zitat von Edlmann (Beitrag 1170836)
Scheint 8 bit Aligned zu sein...Addr(Vecs) mod 16 ist immer 8, Egal an welcher Stelle es deklariert wird (zumindest wenn es ein dynamisches Array ist, festes Array ist gar nicht aligned)

Lokale Variablen sind nunmal nur 8 Bit Byte alligned. :roll:
Zitat:

Delphi-Quellcode:
Vorgabe: {$A8} {$ALIGN 8}



[edit]
Mist, hier quatscht irgendwer ständig von Bit, da hab'sch mich glatt anstecken lassen :oops:

Edlmann 14. Jun 2012 14:28

AW: [ASM / SSE] Vektoroperationen
 
Stimmt, sind natürlich byte...
zum alignment: Schon klar das lokale Variablen nur 8 byte-aligned sind, doch wenn ich nun vor meinem array eine Variable von 1 byte größe Deklariere, müsste doch rein Logisch das Array 8 byte weiter hinten und damit im 16-byte alignment liegen...tuts aber nicht ^^ versuch grad das ganze ohne array, nur mit pointer unt GetMem zu implementieren, denn GetMem ist ja 16 byte Aligned.

himitsu 14. Jun 2012 14:38

AW: [ASM / SSE] Vektoroperationen
 
Der Anfang eines statischen Arrays ist erstmal alligned, so wie es FastMM vorgibt.

Und dann kommt es auf die Größe der Felder des Arrays an.
Und bei Records (innerhalb des Arrays) auf die Größe des größten enthaltenen Feldes.

Die Felder des Arrays werden mit einem maximalen "allign" ausgerichtet, wie es {$ALIGN} vorgibt.
Die tatsächliche Ausrichtung hängt aber von den Array-Feldern ab.
Und ein PACKED ARRAY wird nie ausgerichtet.

Ein Array of Word wird "intern" mit
Delphi-Quellcode:
Min(SizeOf(Word), {$ALIGN})
ausgerichtet, also mit 2, denn mehr ist sinnlos.

Oben der Record besteht aus 4 Singles und da SizeOf(Singe) nur 4 ist, wird das Array intern auch nur mit maximal 4 Byte ausgerichtet.

Edlmann 14. Jun 2012 14:45

AW: [ASM / SSE] Vektoroperationen
 
Vielen Dank für die Erklärung.

Habs mit 16 byte Alignment am Laufen, doch der Gewinn an Geschwindigkeit beträgt +-0... werd mir also den Aufwand damit sparen, und es bei ganz normalen Arrays lassen.

Allerdings hab ich noch immer keine alternative zu addpsh ( Vertikales aufaddieren eines MMX Registers ) gefunden, falls man nur mit SSE1 arbeiten möchte...Hat da jemand einen Plan, wie man das effizient realisieren kann? Der von mir oben vorgestellte Lösungsansatz ist nicht wirklich schneller als das ganze ohne SSE zu machen.

himitsu 14. Jun 2012 14:50

AW: [ASM / SSE] Vektoroperationen
 
Was mir aber noch einfällt.

Gut, die GPU mag schneller sein, als das SSE,
aber wenn nicht viel/lange gerechnet wird, lohnt es sich dann übberhaupt die GPU zu nutzen?

Schlißelich muß ja erstmal alles zu der rüber und danach dann wieder zurück.
Nicht alle GPUs unterstüzen sowas und ich glaub nicht, daß alle aktuellen GPUs eine einheitliche Schnittstelle anbieten. (is doch bestimmt schlimmer als SSE auf den verschiedenen CPUs)

Desmulator 14. Jun 2012 15:30

AW: [ASM / SSE] Vektoroperationen
 
Zitat:

Zitat von Edlmann (Beitrag 1170880)
Allerdings hab ich noch immer keine alternative zu addpsh ( Vertikales aufaddieren eines MMX Registers ) gefunden, falls man nur mit SSE1 arbeiten möchte...Hat da jemand einen Plan, wie man das effizient realisieren kann? Der von mir oben vorgestellte Lösungsansatz ist nicht wirklich schneller als das ganze ohne SSE zu machen.

Nein. Gibt es nicht. Genau da setzte vorhin ja meine Kritik an. Elementare Funktionen sind nicht vorhanden. Es gibt auch keinen wirklich sinnvollen Umweg. Entweder man macht es per Hand mit der FPU oder man sortiert aufwendig irgendwelche einzellnen Singles umher. Ob das SSE 1.0 überhaupt kann weiß ich jedoch nicht.

http://softpixel.com/~cwright/programming/simd/sse.php

Zitat:

Gut, die GPU mag schneller sein, als das SSE,
aber wenn nicht viel/lange gerechnet wird, lohnt es sich dann übberhaupt die GPU zu nutzen?
Lohnt es sich dann, sich in SSE einzuarbeiten oder reicht die heutzutage durchaus schnelle Pipeline der normalen CPU/FPU?

Edlmann 14. Jun 2012 15:58

AW: [ASM / SSE] Vektoroperationen
 
Ja, das hin und herschieben geht, mit SHUFPS, ist schon seit SSE1 dabei - Der Ansatz fürs Kreuzprodukt den ich weiter oben gepostet hatte benutzt das ganze auch.

Desmulator 14. Jun 2012 16:04

AW: [ASM / SSE] Vektoroperationen
 
shufps bringt dir garnichts. Damit kannst du nur die Reihenfolge innerhalb ändern. Aber du musst alle in einer Zeile addieren. Eigentlich müssten alle aus der ersten Zeile in die erste Spalte geschoben werden, alle aus der zweiten Zeile in die zweite Spalte usw. Dann könntest du addieren, aber das ist afaik nicht möglich.

Edlmann 14. Jun 2012 16:39

AW: [ASM / SSE] Vektoroperationen
 
Naja, es ginge, allerdings bräuchte man 4 Register. Das 1ste bleibt wies ist, beim 2. Packst du das 2. Element an 1. Stelle mit shufps, beim 3. das 3., beim 4. das 4., und 3 additionen später hätte man das Ergebnis an der 1sten Stelle des StartRegisters (wenn man all die geshuffelten Register aufs Startregister addiert). Wär aber sowas von umständlich.

[Edit]

Was mir noch fehlt ist ein wenig Dokumentation zu System.TestSSE...weiß jemand von euch was die weiteren Bits davon bedeuten? Auf meinem i5-760 mit SSE bis zu 4.2 enthält TestSSE 127, der einzige KOmmentar dazu sagt jedoch dass nur Bits 1 und 2 Bedeutung hätten...1 für SSE 1, 2 für SSE2...

[Edit 2]

Okey, TestSSE macht nichts anderes, als die CPUID bits auszulesen, wobei hier das 25. Bit für SSE und das 26. für SSE2 steht. Anscheinend gibt es keine wirkliche Möglichkeit, SSE3 Unterstützung auszulesen


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:23 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz