Egy korábbi fogalom bemutatta a csomagolt lebegőpontos értékeket: több számot, amelyek egyetlen xmm regiszterben vannak, és amelyeken sávonként, párhuzamosan hajtjuk végre a műveleteket.
Ugyanezek az xmm regiszterek csomagolt egész számokat is tárolhatnak.
A SIMD lebegőpontos modelljének nagy része változatlanul átvihető a csomagolt egészekre:
A szintaxis azonban némileg eltér:
p előtag, amely jelzi, hogy az utasítás csomagolt adatokon dolgozik.add, mul stb.).A csomagolt egészeknek négy sávméretük van, és mindegyiknek saját utótagja van:
| sávszélesség | bájt | sávok 128 bitben | utótag |
|---|---|---|---|
| bájt | 1 | 16 | b |
| word | 2 | 8 | w |
| dword | 4 | 4 | d |
| qword | 8 | 2 | q |
Például:
| utasítás | jelentés |
|---|---|
paddb |
csomagolt add, 8 bites sávok (16 sáv) |
paddw |
csomagolt add, 16 bites sávok (8 sáv) |
paddd |
csomagolt add, 32 bites sávok (4 sáv) |
paddq |
csomagolt add, 64 bites sávok (2 sáv) |
Egyes utasítások egy méretű sávokat vesznek bemenetként, de más méretűeket adnak kimenetként. Ugyanazt az általános konvenciót követik, de két méretutótaggal. Az első a bemeneti sáv méretét jelzi, a második a kimeneti sáv méretét:
| utasítás | jelentés |
|---|---|
pmovsxwd |
csomagolt movsx, 16 bites sávokból 32 bites sávokba |
pmuldq |
csomagolt mul, 32 bites sávokból 64 bites sávokba |
Két, egész számra utaló nevű utasítás 128 bitet másol egy xmm regiszter és a memória között:
| utasítás | leírás |
|---|---|
movdqa |
csomagolt egészek másolása igazított helyre vagy onnan |
movdqu |
csomagolt egészek másolása igazítatlan helyre vagy onnan |
Úgy viselkednek, mint a movaps és a movups: a movdqa hibát vált ki igazítatlan cím esetén, a movdqu viszont bármelyiket elfogadja.
Mind a négy 128 bitet másol anélkül, hogy értelmezné őket.
Az egész nevet viselő párt konvenció szerint használjuk egész adatokkal, nem kötelező jelleggel.
A dq itt a double-qword rövidítése, azaz 128 bit (16 bájt).
Az összeadás és a kivonás követi az elnevezési szabályt:
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
Nincs külön előjeles és előjel nélküli forma.
A kettes komplemensben az összeadás és a kivonás ugyanazokat a biteket állítja elő, akár előjelesként, akár előjel nélküliként olvassuk a sávokat, így egyetlen utasítás szolgálja mindkettőt.
Az értelmezés rajtad áll, pontosan úgy, mint a skaláris add és sub esetében.
Ezek az utasítások túlcsorduláskor átfordulnak, akárcsak a skaláris megfelelőik.
Egy 8 bites sáv az értékeket 256 modulo szerint tárolja, így a 200 + 100 paddb művelete 300 - 256 = 44-et eredményez, eldobva a bele nem férő biteket.
Vedd észre, hogy azok a plusz bitek nem kerülnek át a következő sávba. Minden sávon külön-külön hajtjuk végre a műveletet, még akkor is, ha ugyanazt a regisztert használják.
Az egész SIMD olyan műveletet ad hozzá, amellyel a lebegőpontos SIMD nem rendelkezik: telítő összeadást és kivonást, amelyek a körbefordulás helyett leszorítanak. A sáv tartományát meghaladó eredmény a sáv által tárolható legnagyobb értékké válik; a tartomány alatti eredmény a legkisebbé.
A telítő formák a méretutótag elé illesztik az s (előjeles) vagy us (előjel nélküli) jelzést:
| utasítás | jelentés |
|---|---|
paddsb |
add, telítő, előjeles 8 bites sávok |
paddusb |
add, telítő, előjel nélküli 8 bites sávok |
psubsw |
sub, telítő, előjeles 16 bites sávok |
psubusw |
sub, telítő, előjel nélküli 16 bites sávok |
A leszorítási tartomány az adott méretű és előjelű egész szám teljes ábrázolható tartománya. Egy bájt esetében:
[0, 255] tartományra szorulnak le: a 200 + 100 paddusb művelete 255-öt ad, az 5 - 10 psubusb művelete pedig 0-t.[-128, 127] tartományra szorulnak le: a 100 + 50 paddsb művelete 127-et ad.A telítés akkor számít, amikor egy sáv korlátos mennyiséget tárol, például egy pixelcsatornát vagy egy hangmintát. A körbefordulás egy túl világos pixelt sötétre változtatna, a leszorítás viszont a maximális fényerőn tartja, ami a kívánt eredmény.
Telítő összeadás és kivonás csak bájt és word sávokhoz létezik, dword vagy qword sávokhoz nem.
Két N bites érték összeszorzása 2N bites szorzatot eredményezhet, a célsáv viszont csak N bit széles. A SIMD műveletek ezt úgy oldják meg, hogy megadják, a szorzat melyik felét kell megtartani: az alsó N bitet vagy a felső N bitet.
A 16 bites sávokhoz három utasítást használunk:
| utasítás | jelentés |
|---|---|
pmullw |
mul, 16 bites sávok, az egyes szorzatok alsó 16 bitjének megtartása |
pmulhw |
mul, 16 bites sávok, a felső 16 bit megtartása, előjeles operandusok |
pmulhuw |
mul, 16 bites sávok, a felső 16 bit megtartása, előjel nélküli operandusok |
Egy szorzat alsó 16 bitje ugyanaz, akár előjelesként, akár előjel nélküliként olvassuk az operandusokat, ezért egyetlen pmullw létezik.
A felső 16 bit azonban az eredmény előjelétől függően változik. Ezért van a felső felet megtartó szorzásnak külön formája előjeles és előjel nélküli szorzásra:
pmulhw, előjeles szorzáshoz.pmulhuw, egy plusz u-val, előjel nélküli szorzáshoz.Figyeld meg a szintaxist:
p, a csomagolt egészre utalva.mul.h, amely jelzi, hogy az eredmény felső („high”) bitjeit választjuk ki.u, ha az eredményt előjel nélküliként kell értelmezni (azaz nincs előjel-kiterjesztve).w méretutótag, amely jelzi, hogy word (16 bites) műveletről van szó.A word sávokra vonatkozó csomagolt szorzás pontosan követi a fenti szabályt.
A dword (32 bites) szorzás is követi a szabályt, de csak az alsó felet kiválasztó változat létezik: pmulld.
Nincs olyan változata a dword szorzásnak, amely a felső biteket választaná ki. Vannak azonban olyan változatok, amelyek kiszélesítik a szorzást, és a páros indexű sávok (azaz a 0. és 2. pozíciójú sávok) teljes 64 bites szorzatát tárolják:
| utasítás | jelentés |
|---|---|
pmuludq |
a páros indexű 32 bites elemek szorzása előjel nélküliként 2 teljes 64 bites szorzattá |
pmuldq |
a páros indexű 32 bites elemek szorzása előjelesként 2 teljes 64 bites szorzattá |
Figyeld meg, hogy a szintaxis a dq-t használja, amit előjel nélküli szorzás esetén esetleg egy u előz meg.
Ez azért van, mert az utasítások dword sávokat fogadnak és qword sávokat adnak ki.
Nincs csomagolt egész osztás. Az ezt igénylő kód az értékeket lebegőpontossá alakítja, elosztja, majd visszaalakítja.
Léteznek csomagolt megfelelői a movsx és movzx utasításnak.
Ugyanazt a szintaxist követik, amelyet azoknál az utasításoknál említettünk, amelyek bemenetének sávmérete eltér a kimenetétől:
pmovsxwd xmm0, xmm1 ; 4 words -> 4 dwords, sign-extended
pmovzxbw xmm0, xmm1 ; 8 bytes -> 8 words, zero-extended
Vedd észre, hogy a sávok számát a nagyobb (kimeneti) szélesség határozza meg.
Az utasítás ennyi sávot olvas be a forrás alsó részéből.
Ez ugyanaz a szemantika, amelyet már láttunk a csomagolt cvt utasításoknál az egyszeres és kétszeres pontosságú lebegőpontosok között.
Léteznek utasítások 32 bites előjeles egészek és lebegőpontos sávok közötti átváltásra.
A cvt és cvtt utasítások szokásos szintaxisát követik, de az si helyett a csomagolt egészt a dq jelöli:
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
Azok az utasítások, amelyek a pi-t használják a csomagolt egészek jelölésére, a régi mmx regiszterekbe írnak, és az x86-64-en gyakorlatilag elavultnak számítanak.
Részesítsd előnyben a dq formákat, amelyek xmm regisztereket használnak.
Ugyanazok a megjegyzések érvényesek itt is, amelyeket a lebegőpontos számok és egészek közötti skaláris átváltásnál tettünk.
A lebegőpontos számokat egy MXCSR nevű speciális regiszter szerint kerekítjük, amelynek módját nem feltételezheted a függvénybe való belépéskor.
Létezik egy cvtt változat (egy plusz t-vel), amely mindig csonkolja az eredményt.
Az is lehetséges, hogy a round segítségével a csomagolt lebegőpontos értékeket ismert állapotba hozzuk az átváltás előtt.
A kerekítés vezérlőértéke ugyanaz, mint a skaláris round esetében, és a szintaxis is ugyanaz a csomagolt lebegőpontos értékeknél:
roundps xmm0, xmm1, 1 ; xmm0 = floor(xmm1), packed 32-bit floats
roundpd xmm2, xmm3, 2 ; xmm2 = ceil(xmm3), packed 64-bit floats
Egy hangstúdió kisméretű keverőpultjának digitális magját írod.
A hang minták folyamaként érkezik. Minden minta egy szám, amely egy hangszórómembrán pillanatnyi helyzetét írja le. A keverőpult egyszerre egy egész mintablokkot dolgoz fel, és mindegyikre ugyanazt a műveletet alkalmazza.
Öt részfeladat vár rád, és mindegyik a memóriában tárolt mintablokkon dolgozik. Az operandusokat memóriacímeken keresztül kapod, az eredményt pedig egy eredménycímre írod.
Ebben a feladatban a számításokat SIMD utasításokkal kell elvégezni, nem skaláris műveletekkel.
A stúdió néha külön sávokra veszi fel a hangszereket. Ahhoz, hogy ugyanazokon a hangszórókon szólaljanak meg, a két sávot összeadod eggyé.
Valósítsd meg a mix_tracks függvényt, amely mintánként összeadja a két sávot.
Minden sáv egy 8 előjeles 16 bites mintából álló blokk.
Az előjeles 16 bites tartományt túllépő összegnek telítődnie kell a körbefordulás helyett.
A függvény argumentumai a következő sorrendben:
result: a kevert 8 minta kiírására szolgáló puffer 16 bájtos határra igazított memóriacíme.track_a: az első sáv 16 bájtos határra igazított memóriacíme, 8 előjeles 16 bites mintával.track_b: a második sáv 16 bájtos határra igazított memóriacíme, 8 előjeles 16 bites mintával.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}
A függvénynek nincs visszatérési értéke.
Amikor két mikrofon egyszerre vesz fel, mindegyik gyengén felveszi a másik forrásának egy másolatát. Ezt a nem kívánt másolatot áthallásnak nevezik. Miután megmérted ezt az áthallást, vissza kell vonnod, hogy megtisztítsd a sávot.
Valósítsd meg a remove_bleed függvényt, amely mintánként kivonja az áthallásblokkot a sávblokkból.
Mindkét blokk 8 előjeles 16 bites minta, és a kivonásnak telítődnie kell.
A függvény argumentumai a következő sorrendben:
result: memóriacím, ahová a megtisztított 8 minta kerül (az igazítás nem garantált).track: a felvett sáv 16 bájtos határra igazított memóriacíme, 8 előjeles 16 bites minta.bleed: az eltávolítandó áthallás memóriacíme, 8 előjeles 16 bites minta (az igazítás nem garantált).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}
A függvénynek nincs visszatérési értéke.
Minden csatorna fölött egy szintmérő található, egy kis sáv, amelynek fényessége mutatja, milyen hangos az adott csatorna. Az előlapon egyetlen fő mérő van, amelynek olyan fényesen kell világítania, mint a két csatorna együttvéve. Ezt a két mérőblokk összeadásával vezérled.
Figyeld meg, hogy a fényes mérő a maximális fényerőn marad ahelyett, hogy visszafordulna a sötétbe, így az összeg telítődik.
Valósítsd meg a combine_meters függvényt, amely mintánként összeadja a két mérőblokkot.
A függvény argumentumai a következő sorrendben:
result: 16 bájtos határra igazított memóriacím, ahová a 16 összevont érték kerül.meter_a: az első csatorna mérőblokkjának 16 bájtos határra igazított memóriacíme, 16 csomagolt előjel nélküli 8 bites értékkel.meter_b: a második csatorna mérőblokkjának 16 bájtos határra igazított memóriacíme, 16 csomagolt előjel nélküli 8 bites értékkel.Mindkét blokk 16 előjel nélküli 8 bites érték. Ezeket össze kell adni, az előjel nélküli tartományra telítve.
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}
A függvénynek nincs visszatérési értéke.
A dalnak nem szabad hirtelen véget érnie, hanem a végén el kell halványulnia. Van egy blokk erősítési együtthatód, mintánként egy, amelyek egyre halkabbra, majd még halkabbra csökkennek.
Minden együttható egy 65536-ra vonatkoztatott tört.
Például ha egy minta értéke 16384, az valójában 16384 / 65536-ot fejez ki, ami 1 / 4.
A legnagyobb használható érték, a 32767, éppen a fél alatt van.
Mivel a 65536 éppen 2^16, ha egy mintát megszorzod az együtthatójával, és megtartod a szorzat felső felét, az sample * gain / 65536-ot kapod.
Valósítsd meg az apply_fade függvényt, amely minden mintát a hozzá tartozó együtthatóval skáláz.
A függvény argumentumai a következő sorrendben:
result: 16 bájtos határra igazított memóriacím, ahová az összes skálázott minta kerül.track: a sáv 16 bájtos határra igazított memóriacíme, n előjeles 16 bites mintával.gains: az erősítési együtthatók 16 bájtos határra igazított memóriacíme, n előjeles 16 bites értékkel, mindegyik a 0 és 32767 közötti tartományban.n: a minták száma, amely mindig nem nulla és a 8 többszöröse, 64 bites előjel nélküli egészként.Figyeld meg, hogy a sáv egy tömb, amely n előjeles 16 bites mintát tartalmaz, ahol n soha nem nulla, és mindig a 8 többszöröse.
Minden minta esetén a skálázott eredmény az előjeles szorzat felső 16 bitje: 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}
A felső fél megtartása a negatív végtelen felé kerekít.
Ezért skálázódik a 20000 * 32767 9999-re, míg a -20000 * 32767 -10000-re -9999 helyett.
A függvénynek nincs visszatérési értéke.
A dobost jóval túl hangosan vették fel. Hogy az egész sávot lehalkítsd, minden mintát elosztasz egy konstanssal.
Valósítsd meg az attenuate_track függvényt.
A függvény argumentumai a következő sorrendben:
result: 16 bájtos határra igazított memóriacím, ahová az eredmények 32 bites előjeles egészként kerülnek.samples: a minták 16 bájtos határra igazított memóriacíme, n előjeles 16 bites érték.divisor: az osztó 4 azonos másolatának 16 bájtos határra igazított memóriacíme, 32 bites egészként.n a minták száma, amely mindig nem nulla és a 4 többszöröse, 64 bites előjel nélküli egészként.Figyeld meg, hogy a samples egy tömb, amely n előjeles 16 bites mintát tartalmaz, ahol n soha nem nulla, és mindig a 4 többszöröse.
Az osztót ugyanannak a 32 bites egésznek a 4 másolataként kapod, már replikálva, hogy egyszerre kitölthesse mind a négy sávot.
Az egész számos SIMD-nek nincs csomagolt osztása, ezért a művelet csomagolt lebegőpontos számokkal történik, majd visszaalakítjuk csomagolt 32 bites egészekké, hogy a result-ba kerüljenek.
A visszaalakítás nulla felé csonkítással történik, így a 2.3 hányadosból 2 lesz, a -2.3-ból pedig -2.
samples = {300, 150, 90, -120, 100, 50, -50, 7}
divisor = {3, 3, 3, 3}
result = {100, 50, 30, -40, 33, 16, -16, 2}
A függvénynek nincs visszatérési értéke.
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) x86-64 Assembly nyelvet 22 fogalom130 feladat segítségével, valódi emberi mentorálással, mindez ingyen.