![]() |
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. |
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:
|
AW: [ASM / SSE] Vektoroperationen
Jupp, hab ich... aber müsste es nicht statt
Delphi-Quellcode:
Erst nach dem 2. Compare verringert werden?
CMP ECX, 0
JLE @@exit DEC ECX CMP ECX, $07FFFFFF JG @@exit
Delphi-Quellcode:
Oder macht das an dieser Stelle keinen Unterschied?
CMP ECX, 0
JLE @@exit CMP ECX, $07FFFFFF JG @@exit DEC ECX |
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. |
AW: [ASM / SSE] Vektoroperationen
Okey...ach klar, ist ja JG. Werds trotzdem mal hinter das 2. CMP packen, der übersicht halber.
|
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; |
AW: [ASM / SSE] Vektoroperationen
Zitat:
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.
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:
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. )
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 um die Register EDI und ESI einzubrigen und das rep-Prefix mit all seinen Variationen. ( rep, repne, repnz, .. )
Code:
Fertig. Jetzt schauen wir uns das mal an.
mov edi, Ziel
mov esi, Quelle mov ecx, Anzahl rep movsb
Code:
benötigt ( 3 + Anzahl ) Clocks um fertig zu werden. Die zu dekodierenden Bytes sind zwei. Einmalig.
rep movsb ; 3 + Anzahl Clocks, 2 Byte
Dagegen den anderen Code.
Code:
Ein einziges Byte zu kopieren benötigt also nun 6 Clocks und 9 Bytes die dekodiert werden müssen.
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 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:
Der Vollständigkeit halber:
Code:
Clocks: ( 7 * Anzahl ) + 1, Bytes: 8 * Anzahl
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 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. ![]() 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:
Davon mal abgesehen, du hast bei deinem Beispiel 9000 Zyklen weniger. Was erwartest du denn? :-P Edit: ![]() |
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:
(Nicht meine Implementierung, an der Delphi Version feil ich noch)
__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 } 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? |
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!) |
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 |
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:23 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