Ein früheres Konzept hat gepackte Gleitkommazahlen eingeführt: mehrere Zahlen in einem einzigen xmm-Register, die Lane-weise und parallel verarbeitet werden.
Dieselben xmm-Register können auch gepackte Ganzzahlen aufnehmen.
Das meiste vom SIMD-Gleitkommamodell überträgt sich unverändert auf gepackte Ganzzahlen:
Die Syntax ist allerdings etwas anders:
p, das angibt, dass der Befehl auf gepackten Daten arbeitet.add, mul usw.).Gepackte Ganzzahlen haben vier Lane-Größen, und jede hat ihr eigenes Suffix:
| Lane-Breite | Bytes | Lanes in 128 Bit | Suffix |
|---|---|---|---|
| Byte | 1 | 16 | b |
| Word | 2 | 8 | w |
| Dword | 4 | 4 | d |
| Qword | 8 | 2 | q |
Zum Beispiel:
| Befehl | Bedeutung |
|---|---|
paddb |
gepacktes add, 8-Bit-Lanes (16 Lanes) |
paddw |
gepacktes add, 16-Bit-Lanes (8 Lanes) |
paddd |
gepacktes add, 32-Bit-Lanes (4 Lanes) |
paddq |
gepacktes add, 64-Bit-Lanes (2 Lanes) |
Manche Befehle nehmen Lanes mit einer Größe als Eingabe, geben aber eine andere Größe als Ausgabe aus. Sie folgen derselben allgemeinen Konvention, aber mit zwei Größensuffixen. Das erste gibt die Größe der Eingabe-Lane an, das zweite die Größe der Ausgabe-Lane:
| Befehl | Bedeutung |
|---|---|
pmovsxwd |
gepacktes movsx, von 16-Bit-Lanes zu 32-Bit-Lanes |
pmuldq |
gepacktes mul, von 32-Bit-Lanes zu 64-Bit-Lanes |
Zwei Befehle, die nach Ganzzahlen benannt sind, kopieren 128 Bit zwischen einem xmm-Register und dem Speicher:
| Befehl | Beschreibung |
|---|---|
movdqa |
gepackte Ganzzahlen von oder zu einer ausgerichteten Speicherstelle kopieren |
movdqu |
gepackte Ganzzahlen von oder zu einer nicht ausgerichteten Speicherstelle kopieren |
Sie verhalten sich wie movaps und movups: movdqa löst bei einer fehlausgerichteten Adresse einen Fehler aus, movdqu akzeptiert jede.
Alle vier kopieren 128 Bit, ohne sie zu interpretieren.
Das nach Ganzzahlen benannte Paar wird aus Konvention mit Ganzzahldaten verwendet, nicht aus Notwendigkeit.
dq steht hier für double-qword, d. h. 128 Bit (16 Byte).
Addition und Subtraktion folgen der Namensregel:
paddb xmm0, xmm1 ; 16 lanes: each 8-bit, xmm0 += xmm1
paddw xmm2, xmm3 ; 8 lanes: each 16-bit, xmm2 += xmm3
psubd xmm4, xmm5 ; 4 lanes: each 32-bit, xmm4 -= xmm5
psubq xmm6, xmm7 ; 2 lanes: each 64-bit, xmm6 -= xmm7
Es gibt keine getrennte Form für vorzeichenbehaftet und vorzeichenlos.
Im Zweierkomplement erzeugen Addition und Subtraktion dieselben Bits, egal ob die Lanes als vorzeichenbehaftet oder vorzeichenlos gelesen werden, deshalb kommt ein einziger Befehl für beide aus.
Die Interpretation liegt bei dir, genau wie bei den skalaren add und sub.
Diese Befehle umlaufen bei Überlauf, wie ihre skalaren Gegenstücke.
Eine 8-Bit-Lane hält Werte modulo 256, also ergibt ein paddb von 200 + 100 den Wert 300 - 256 = 44, wobei die nicht passenden Bits verworfen werden.
Beachte, dass diese zusätzlichen Bits nicht in die nächste Lane übergehen. Jede Lane wird unabhängig von den anderen verarbeitet, obwohl sie dasselbe Register teilen.
SIMD für Ganzzahlen fügt eine Operation hinzu, die SIMD für Gleitkommazahlen nicht hat: sättigende Addition und Subtraktion, die begrenzen statt umzulaufen. Ein Ergebnis über dem Bereich der Lane wird der größte Wert, den die Lane halten kann; ein Ergebnis unter dem Bereich wird der kleinste.
Die sättigenden Formen fügen s (vorzeichenbehaftet) oder us (vorzeichenlos) vor dem Größensuffix ein:
| Befehl | Bedeutung |
|---|---|
paddsb |
add, sättigend, vorzeichenbehaftete 8-Bit-Lanes |
paddusb |
add, sättigend, vorzeichenlose 8-Bit-Lanes |
psubsw |
sub, sättigend, vorzeichenbehaftete 16-Bit-Lanes |
psubusw |
sub, sättigend, vorzeichenlose 16-Bit-Lanes |
Der Begrenzungsbereich ist der volle darstellbare Bereich für eine Ganzzahl der entsprechenden Größe und Vorzeichenbehaftung. Für ein Byte:
[0, 255] begrenzt: Ein paddusb von 200 + 100 ergibt 255, und ein psubusb von 5 - 10 ergibt 0.[-128, 127] begrenzt: Ein paddsb von 100 + 50 ergibt 127.Sättigung ist wichtig, wenn eine Lane eine begrenzte Größe enthält, etwa einen Pixelkanal oder einen Abtastwert. Ein umlaufender Wert würde ein zu helles Pixel dunkel machen, während die Begrenzung es auf maximaler Helligkeit hält, was das gewünschte Ergebnis ist.
Sättigende Addition und Subtraktion gibt es nur für Byte- und Word-Lanes, nicht für Dword- oder Qword-Lanes.
Die Multiplikation zweier N-Bit-Werte kann ein 2N-Bit-Produkt ergeben, aber die Ziel-Lane ist nur N Bit breit. SIMD-Operationen lösen das, indem sie angeben, welche Hälfte des Produkts behalten wird, entweder die unteren N Bits oder die oberen N Bits.
Für 16-Bit-Lanes werden drei Befehle verwendet:
| Befehl | Bedeutung |
|---|---|
pmullw |
mul, 16-Bit-Lanes, die unteren 16 Bits jedes Produkts behalten |
pmulhw |
mul, 16-Bit-Lanes, die oberen 16 Bits behalten, vorzeichenbehaftete Operanden |
pmulhuw |
mul, 16-Bit-Lanes, die oberen 16 Bits behalten, vorzeichenlose Operanden |
Die unteren 16 Bits eines Produkts sind gleich, egal ob die Operanden als vorzeichenbehaftet oder vorzeichenlos gelesen werden, deshalb gibt es nur ein einziges pmullw.
Die oberen 16 Bits hängen jedoch von der Vorzeichenbehaftung des Ergebnisses ab. Deshalb hat die Multiplikation der oberen Hälfte getrennte Formen für vorzeichenbehaftete und vorzeichenlose Multiplikation:
pmulhw, für vorzeichenbehaftete Multiplikation.pmulhuw, mit einem zusätzlichen u, für vorzeichenlose Multiplikation.Beachte die Syntax:
p, für gepackte Ganzzahl.mul.h, das angibt, dass die oberen („hohen“) Bits des Ergebnisses ausgewählt werden.u, wenn das Ergebnis als vorzeichenlos interpretiert werden soll (d. h. es wird nicht vorzeichenerweitert).w, das angibt, dass es sich um eine Word-Operation (16 Bit) handelt.Die gepackte Multiplikation für Words folgt genau der obigen Regel.
Die Dword-Multiplikation (32 Bit) folgt ebenfalls der Regel, aber es gibt nur die Variante, die die untere Hälfte auswählt: pmulld.
Es gibt keine Variante der Dword-Multiplikation, die die oberen Bits auswählt. Es gibt jedoch Varianten, die die Multiplikation verbreitern und das volle 64-Bit-Produkt von geradzahlig indizierten Lanes speichern (d. h. der Lanes an Position 0 und 2):
| Befehl | Bedeutung |
|---|---|
pmuludq |
die geradzahlig indizierten 32-Bit-Elemente vorzeichenlos multiplizieren, in 2 volle 64-Bit-Produkte |
pmuldq |
die geradzahlig indizierten 32-Bit-Elemente vorzeichenbehaftet multiplizieren, in 2 volle 64-Bit-Produkte |
Beachte, dass die Syntax dq verwendet, gegebenenfalls mit einem u davor für vorzeichenlose Multiplikation.
Das liegt daran, dass die Befehle dword-Lanes als Eingabe nehmen und qword-Lanes ausgeben.
Es gibt keine Division für gepackte Ganzzahlen. Code, der sie braucht, wandelt die Werte in Gleitkommazahlen um, dividiert und wandelt sie zurück.
Es gibt gepackte Entsprechungen für movsx und movzx.
Sie folgen derselben Syntax, die wir für Befehle erwähnt haben, deren Eingaben eine andere Lane-Größe haben als ihre Ausgaben:
pmovsxwd xmm0, xmm1 ; 4 words -> 4 dwords, sign-extended
pmovzxbw xmm0, xmm1 ; 8 bytes -> 8 words, zero-extended
Beachte, dass die Anzahl der Lanes durch die größere (Ausgabe-)Breite festgelegt wird.
Der Befehl liest so viele Lanes aus dem unteren Teil der Quelle.
Das ist dieselbe Semantik, die wir bereits bei gepackten cvt-Befehlen zwischen Gleitkommazahlen mit einfacher und doppelter Genauigkeit gesehen haben.
Es gibt Befehle, um zwischen vorzeichenbehafteten 32-Bit-Ganzzahlen und Gleitkomma-Lanes zu konvertieren.
Sie folgen der üblichen Syntax für cvt- und cvtt-Befehle, aber statt si wird eine gepackte Ganzzahl durch dq dargestellt:
cvtdq2ps xmm0, xmm1 ; convert 32-bit signed integers in xmm1 to 32-bit floats in xmm0
cvtdq2pd xmm2, xmm3 ; convert 32-bit signed integers in xmm3 to 64-bit floats in xmm2
cvtps2dq xmm4, xmm5 ; convert 32-bit floats in xmm5 to 32-bit signed integers in xmm4
Die Befehle, die pi verwenden, um gepackte Ganzzahlen darzustellen, schreiben in veraltete mmx-Register und sind auf x86-64 praktisch veraltet.
Bevorzuge die dq-Formen, die xmm-Register verwenden.
Dieselben Anmerkungen, die zur skalaren Konvertierung zwischen Gleitkommazahlen und Ganzzahlen gemacht wurden, gelten auch hier.
Gleitkommazahlen werden gemäß einem speziellen Register namens MXCSR gerundet, dessen Modus du beim Eintritt in die Funktion nicht voraussetzen kannst.
Es gibt eine cvtt-Variante (mit einem zusätzlichen t), die das Ergebnis immer abschneidet.
Es ist auch möglich, round zu verwenden, um gepackte Gleitkommawerte vor der Konvertierung in einen bekannten Zustand zu bringen.
Der Rundungssteuerwert ist derselbe wie beim skalaren round, und die Syntax ist für gepackte Gleitkommawerte ebenfalls dieselbe:
roundps xmm0, xmm1, 1 ; xmm0 = floor(xmm1), packed 32-bit floats
roundpd xmm2, xmm3, 2 ; xmm2 = ceil(xmm3), packed 64-bit floats
Du schreibst den digitalen Kern eines kleinen Mischpults in einem Tonstudio.
Ton kommt als Strom von Samples an. Jedes Sample ist eine Zahl, die die Position der Lautsprechermembran zu einem Zeitpunkt beschreibt. Ein Mischpult verarbeitet einen ganzen Block von Samples auf einmal und wendet auf jedes dieselbe Operation an.
Du hast fünf Aufgaben, die jeweils auf einem im Speicher liegenden Block von Samples arbeiten. Du erhältst die Operanden über Speicheradressen und schreibst dein Ergebnis über eine Ergebnisadresse.
Die Berechnungen in dieser Übung sollten mit SIMD-Befehlen durchgeführt werden, nicht mit skalaren Operationen.
Das Studio nimmt Instrumente manchmal auf verschiedenen Spuren auf. Um sie über dieselben Lautsprecher abzuspielen, summierst du die beiden Spuren zu einer.
Implementiere die Funktion mix_tracks, die zwei Spuren Sample für Sample addiert.
Jede Spur ist ein Block aus 8 vorzeichenbehafteten 16-Bit-Samples.
Eine Summe, die den vorzeichenbehafteten 16-Bit-Bereich überläuft, muss begrenzt werden statt umzuschlagen.
Diese Funktion nimmt folgende Argumente in dieser Reihenfolge entgegen:
result: 16-Byte-ausgerichtete Speicheradresse für einen Puffer, in den die 8 gemischten Samples geschrieben werden.track_a: 16-Byte-ausgerichtete Speicheradresse der ersten Spur mit 8 vorzeichenbehafteten 16-Bit-Samples.track_b: 16-Byte-ausgerichtete Speicheradresse der zweiten Spur mit 8 vorzeichenbehafteten 16-Bit-Samples.track_a = {10000, 30000, 20000, -30000, -20000, -100, 100, 0}
track_b = { 5000, 5000, 20000, -5000, -20000, 100, -100, 0}
result = {15000, 32767, 32767, -32768, -32768, 0, 0, 0}
Diese Funktion hat keinen Rückgabewert.
Wenn zwei Mikrofone gleichzeitig aufnehmen, fängt jedes eine schwache Kopie der Quelle des anderen auf. Diese unerwünschte Kopie heißt Übersprechen. Sobald dieses Übersprechen gemessen wurde, muss es wieder abgezogen werden, um die Spur zu bereinigen.
Implementiere die Funktion remove_bleed, die den Übersprechblock Sample für Sample vom Spurblock subtrahiert.
Jeder Block besteht aus 8 vorzeichenbehafteten 16-Bit-Samples, und die Subtraktion muss sättigen.
Diese Funktion nimmt folgende Argumente in dieser Reihenfolge entgegen:
result: Speicheradresse, an die die 8 bereinigten Samples geschrieben werden (Ausrichtung nicht garantiert).track: 16-Byte-ausgerichtete Speicheradresse der aufgenommenen Spur, 8 vorzeichenbehaftete 16-Bit-Samples.bleed: Speicheradresse des zu entfernenden Übersprechens, 8 vorzeichenbehaftete 16-Bit-Samples (Ausrichtung nicht garantiert).track = {10000, -30000, 5000, 32767, 0, 200, -200, 15000}
bleed = { 2000, 5000, 5000, -5, 0, 300, 300, 5000}
result = { 8000, -32768, 0, 32767, 0, -100, -500, 10000}
Diese Funktion hat keinen Rückgabewert.
Über jedem Kanal sitzt eine Pegelanzeige, ein kleiner Balken, dessen Helligkeit zeigt, wie laut dieser Kanal ist. Die Frontplatte hat eine Hauptpegelanzeige, die so hell leuchten soll wie die beiden Kanäle zusammen. Du addierst die beiden Pegelblöcke, um die Hauptpegelanzeige anzusteuern.
Beachte, dass eine helle Pegelanzeige auf voller Helligkeit bleibt, statt wieder dunkel zu werden, daher ist die Summe sättigend.
Implementiere die Funktion combine_meters, die zwei Pegelblöcke Sample für Sample addiert.
Diese Funktion nimmt folgende Argumente in dieser Reihenfolge entgegen:
result: 16-Byte-ausgerichtete Speicheradresse, an die die 16 kombinierten Werte geschrieben werden.meter_a: 16-Byte-ausgerichtete Speicheradresse des Pegelblocks des ersten Kanals mit 16 gepackten vorzeichenlosen 8-Bit-Werten.meter_b: 16-Byte-ausgerichtete Speicheradresse des Pegelblocks des zweiten Kanals mit 16 gepackten vorzeichenlosen 8-Bit-Werten.Jeder Block besteht aus 16 vorzeichenlosen 8-Bit-Werten. Sie sollten addiert werden, wobei auf den vorzeichenlosen Bereich gesättigt wird.
meter_a = {200, 100, 50, 255, 0, 128, 10, 240, 1, 2, 3, 4, 5, 6, 7, 8}
meter_b = {100, 100, 200, 1, 0, 128, 10, 20, 1, 2, 3, 4, 5, 6, 7, 8}
result = {255, 200, 250, 255, 0, 255, 20, 255, 2, 4, 6, 8, 10, 12, 14, 16}
Diese Funktion hat keinen Rückgabewert.
Der Song soll nicht abrupt enden, sondern am Ende ausblenden. Du hast einen Block von Verstärkungskoeffizienten, einen pro Sample, die von leiser zu noch leiser abnehmen.
Jeder Koeffizient ist ein Bruch, der über 65536 ausgedrückt wird.
Wenn ein Sample zum Beispiel den Wert 16384 hat, drückt es tatsächlich 16384 / 65536 aus, also 1 / 4.
Der größte nutzbare Wert, 32767, liegt knapp unter der Hälfte.
Da 65536 gleich 2^16 ist, ergibt die Multiplikation eines Samples mit seinem Koeffizienten und das Behalten der oberen Hälfte des Produkts sample * gain / 65536.
Implementiere die Funktion apply_fade, die jedes Sample mit seinem Koeffizienten skaliert.
Diese Funktion nimmt folgende Argumente in dieser Reihenfolge entgegen:
result: 16-Byte-ausgerichtete Speicheradresse, an die alle skalierten Samples geschrieben werden.track: 16-Byte-ausgerichtete Speicheradresse der Spur mit n vorzeichenbehafteten 16-Bit-Samples.gains: 16-Byte-ausgerichtete Speicheradresse der Verstärkungskoeffizienten mit n vorzeichenbehafteten 16-Bit-Werten, jeweils im Bereich 0 bis 32767.n: die Anzahl der Samples, die immer ungleich null und ein Vielfaches von 8 ist, als vorzeichenlose 64-Bit-Ganzzahl.Beachte, dass die Spur ein Array ist, das n vorzeichenbehaftete 16-Bit-Samples enthält, wobei n nie null und immer ein Vielfaches von 8 ist.
Für jedes Sample ist das skalierte Ergebnis die hohen 16 Bits des vorzeichenbehafteten Produkts: sample * gain.
track = {20000, 20000, 20000, 20000, -20000, -20000, -20000, -20000}
gains = {32767, 24576, 16384, 8192, 32767, 24576, 16384, 8192}
result = { 9999, 7500, 5000, 2500, -10000, -7500, -5000, -2500}
Die obere Hälfte zu behalten rundet in Richtung negative Unendlichkeit.
Deshalb skaliert 20000 * 32767 zu 9999, während -20000 * 32767 zu -10000 statt zu -9999 skaliert.
Diese Funktion hat keinen Rückgabewert.
Der Schlagzeuger wurde viel zu laut aufgenommen. Um die ganze Spur herunterzuziehen, teilst du jedes Sample durch eine Konstante.
Implementiere die Funktion attenuate_track.
Diese Funktion nimmt folgende Argumente in dieser Reihenfolge entgegen:
result: 16-Byte-ausgerichtete Speicheradresse, an die die Ergebnisse als vorzeichenbehaftete 32-Bit-Ganzzahlen geschrieben werden.samples: 16-Byte-ausgerichtete Speicheradresse der Samples, n vorzeichenbehaftete 16-Bit-Werte.divisor: 16-Byte-ausgerichtete Speicheradresse von 4 identischen Kopien des Divisors als 32-Bit-Ganzzahlen.n ist die Anzahl der Samples, die immer ungleich null und ein Vielfaches von 4 ist, als vorzeichenlose 64-Bit-Ganzzahl.Beachte, dass samples ein Array ist, das n vorzeichenbehaftete 16-Bit-Samples enthält, wobei n nie null und immer ein Vielfaches von 4 ist.
Der Divisor wird als 4 Kopien derselben 32-Bit-Ganzzahl bereitgestellt, bereits repliziert, damit er alle vier Lanes auf einmal füllen kann.
Ganzzahl-SIMD hat keine gepackte Division, daher wird die Arbeit in gepackten Gleitkommazahlen erledigt und dann zurück in gepackte 32-Bit-Ganzzahlen umgewandelt, um in result gespeichert zu werden.
Wandle zurück, indem du gegen Null abschneidest, sodass ein Quotient von 2.3 zu 2 wird und -2.3 zu -2.
samples = {300, 150, 90, -120, 100, 50, -50, 7}
divisor = {3, 3, 3, 3}
result = {100, 50, 30, -40, 33, 16, -16, 2}
Diese Funktion hat keinen Rückgabewert.
Melde dich bei Exercism an, um x86-64 Assembly mit 22 Konzepte130 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.