Einzelnen Beitrag anzeigen

Benutzerbild von Desmulator
Desmulator

Registriert seit: 3. Mai 2007
Ort: Bonn
169 Beiträge
 
#17

AW: [ASM / SSE] Vektoroperationen

  Alt 14. Jun 2012, 10:27
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.

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?

Edit: http://www.bernd-leitenberger.de/cisc-risc.shtml super artikel zu CISC und RISC
Lars
There are 10 kinds of people in the world:
those who get binary, and those who don’t.

Geändert von Desmulator (14. Jun 2012 um 10:47 Uhr)
  Mit Zitat antworten Zitat