Obsah
1. SIMD operace a mikroprocesory s RISCovou architekturou
2. Od technologie VFP k Advanced SIMD (NEON)
3. Pracovní registry používané instrukcemi NEON
4. Použitá terminologie: vector, lane, element
5. Podporované formáty prvků vektorů
6. Instrukce určené pro zpracování skalárních dat
7. Formát instrukcí NEON, prefixy a suffixy u instrukcí
8. Konverze operandů (rozšíření, zmenšení), operace se saturací
9. Příklad různých variant instrukce ADD
10. Typy podporovaných vektorových instrukcí, aritmetické a logické instrukce
11. Instrukce určené pro provedení jednoho kroku delší operace
12. Podpora v GCC pro základní vektorové operace
13. Překlad do strojového kódu se zákazem SIMD instrukcí
14. Překlad do strojového kódu s povolením SIMD instrukcí
15. Základní vektorové operace s prvky vektorů typu float a double
16. Překlad do strojového kódu se zákazem SIMD instrukcí
17. Překlad do strojového kódu s povolením SIMD instrukcí
18. Znovuzrození datového typu single/float?
19. Repositář s demonstračními příklady
1. SIMD operace a mikroprocesory s RISCovou architekturou
Prakticky každá významnější společnost (v případě mikroprocesorů řady PowerPC se pak dokonce jednalo o alianci několika společností) navrhující mikroprocesory s architekturou RISC přišla dříve či později na trh s instrukční sadou obsahující „vektorové“ instrukce, které jsou dnes souhrnně označovány zkratkou SIMD (původní vektorové instrukce používané na superpočítačích jsou v některých ohledech flexibilnější, proto budeme používat spíše poněkud přesnější zkratku SIMD znamenající „single instruction – multiple data“, viz též například úvodní článek).
Rozšiřující instrukční sady byly pojmenovávány nejrůznějšími názvy a zkratkami a nikdy vlastně nedošlo – částečně na rozdíl od platformy x86 – ke sjednocení těchto instrukcí do jediné skupiny „SIMD pro RISC“, což je vlastně logické, protože procesory RISC jsou mnohdy určeny pro specializované oblasti použití, od vestavných (embedded) systémů přes smartphony a tablety až po superpočítače.
Nejvýznamnější implementace rozšiřujících instrukcí typu SIMD na mikroprocesorech s architekturou RISC, ať již se jedná o instrukce určené pro operace s celými čísly či s čísly reálnými (přesněji řečeno s plovoucí řádovou čárkou), jsou pro větší přehlednost vypsány v následující tabulce:
# | Zkratka/název | Plný název | Rodina procesorů |
---|---|---|---|
1 | MAX-1 | Multimedia Acceleration eXtensions v1 | HP-PA RISC |
2 | MAX-2 | Multimedia Acceleration eXtensions v2 | HP-PA RISC |
3 | VIS 1 | Visual Instruction v1 | Set SPARC V9 |
4 | VIS 2 | Visual Instruction v2 | Set SPARC V9 |
5 | AltiVec | (obchodní názvy Velocity Engine, VMX) | PowerPC |
6 | MDMX | MIPS Digital Media eXtension (MaDMaX) | MIPS |
7 | MIPS-3D | MIPS-3D | MIPS |
8 | MVI | Motion Video Instructions | DEC Alpha |
9 | NEON | Advanced SIMD | Cortex (ARMv7, ARMv8) |
10 | Packed SIMD | Packed SIMD | RISC-V |
11 | Vector Set | Vector Set | RISC-V |
12 | SVE | Scalable Vector Extension | ARMv8.2-A a novější |
V dnešním článku nás bude zajímat především technologie nazvaná NEON, resp. celým názvem Advanced SIMD. Ta je určena pro mikroprocesory (a mikrořadiče) z rodiny ARM s 32bitovými i 64bitovými jádry a typicky tuto technologii nalezneme v jádrech ARM Cortex-A. Existuje několik variant implementace NEONu:
Jádro | Povinné? | Šířka vektorů |
---|---|---|
ARM Cortex-A5 | ne | 64 bitů |
ARM Cortex-A7 | ano | 64 bitů |
ARM Cortex-A8 | ano | 64 bitů |
ARM Cortex-A9 | ne | 64 bitů |
ARM Cortex-A12 | ano | 128 bitů |
ARM Cortex-A15 | ano | 128 bitů |
ARM Cortex-A17 | ano | 128 bitů |
ARM Cortex-A53 | ano | 128 bitů |
ARM Cortex-A57 | ano | 128 bitů |
2. Od technologie VFP k Advanced SIMD (NEON)
Pokud se podíváme na historii vzniku a vývoje RISCových mikroprocesorů ARM, zjistíme, že cesta k technologii NEON na původních 32bitových a následně i 64bitových jádrech vlastně nebyla vůbec přímočará. První implementace „vektorových“ operací určených pro mikroprocesory ARM používaly rozhraní pro volitelné koprocesory, takže se vlastně používala paralelní/doplňková instrukční sada (což je vlastně podobné situaci na platformě x86 s matematickými koprocesory 80×87). Konkrétně se jednalo o technologii nazvanou VFP neboli Vector Floating Point. Touto technologií, která je stále na některých ARMovských jádrech podporována, jsme se již kdysi zabývali v seriálu o architekturách počítačů, a to konkrétně v tomto článku.
Nástupce VFP, tedy technologie NEON, již podporovala plnohodnotné SIMD operace, konkrétně při použití vektorů s nejmenšími prvky o velikosti jednoho bajtu až šestnáct operací (například součtu) paralelně. Díky tomu bylo možné implementovat například dekodér pro známý formát MP3 na mikroprocesoru s taktem pouhých 10 MHz, popř. implementovat AMR kodek na podobném čipu, ovšem s hodinovým taktem 13 MHz. Při použití klasických výpočtů se skalárními hodnotami by bylo nutné použít čip s vyšší hodinovou frekvencí či naopak – tento výkonný čip by již neměl dostatek výkonu pro provádění dalších činností. Pro ukázku: ještě mikroprocesory 486DX2 s hodinovou frekvencí 66 MHz měly s dekódováním MP3 velké problémy a zvládaly jen menší bitrate a monofonní výstup). Právě díky NEONu se začaly čipy ARM používat i v některých oblastech, které byly dříve vyhrazeny digitálním signálovým procesorům (a dnes jsou VLIW jádra DSP kombinována právě s ARMem).
Mimo technologií VFP a NEON bylo pro některá (dnes již notně stará) jádra ARM vyvinuto rozšíření pro DSP operace. Konkrétně se jednalo o jádra ARMv5TE. Nové instrukce zavedené v rámci tohoto rozšíření byly využitelné například při kódování a dekódování videa, při zpracování zvukového signálu (včetně zvukové syntézy), práci s rastrovými obrazy (image processing) atd. Instrukce typu Load & Store mohly pracovat s registrovými páry, zavedeny byly nové adresovací režimy, aritmetika se saturací (tj. bez přetečení) a taktéž instrukce typu „multiply and accumulate (MAC)“ 16×16 bitů a 32×16 bitů, které mohly být v instrukční pipeline vykonány v rozmezí jednoho taktu (v jejich průběhu se tedy mohly začít zpracovávat další instrukce).
Uvádí se, že při zpracování signálů byla výkonnost nových instrukcí v jádrech ARMv5TE dvakrát až třikrát vyšší, než při použití „běžných“ jader ARMv5 (samozřejmě za předpokladu ruční optimalizace kódu, což ostatně až na některé výjimky platí dodnes, protože intrinsic nejsou ve všech případech samospasitelné).
Další odbočkou byla technologie SIMD extensions for multimedia pro jádra ARMv6, která byla později nahrazena Advanced SIMD (NEONem).
Podle specifikace by všechny standardní implementace jader ARMv8 (s instrukční sadou AArch64, tedy zjednodušeně řečeno „64bitové ARMy“) měly technologii NEON podporovat, stejně jako operace s FP hodnotami (jinými slovy – už není nutné řešit například problém „hardfp versus softfp“). Ovšem v budoucnosti se pravděpodobně setkáme i se specializovanými jádry nakonfigurovanými odlišným způsobem, například:
- NEON nebude vůbec podporován.
- FP operace nebudou vůbec podporovány.
- NEON+FP budou podporovány, ale bez zpracování výjimek u FP operací.
Pro ARMv7 (stále velmi často používaný) mohou nastat všechny možné kombinace jednotek NEON a VFP (existencí jednotky je myšlen fakt, že je na ploše mikroprocesoru implementována):
- Bez jednotky NEON, bez jednotky VFP
- S jednotkou NEON, ale bez jednotky VFP
- Bez jednotky NEON, ale s jednotkou VFP
- S jednotkou NEON i s jednotkou VFP
Zajímavá je situace ve chvíli, kdy existuje jednotka NEON, ale nikoli jednotka VFP. V takovém případě není možné využívat operace s plovoucí řádovou čárkou, takže se (opět velmi zjednodušuji) z NEONu stává obdoba MMX (což pro mnoho aplikací dostačuje).
Pro úplnost si ještě uveďme stručné porovnání technologie NEON s technologiemi SSE a Altivec (zmíněných v úvodní kapitole):
Vlastnost | NEON | SSE | Altivec |
---|---|---|---|
Registry | 32×64 bitů | 8×128 bitů (+8 v x86–64) | 32×128 bitů |
Typ operací | 3 operandy (registry) | registry/paměť | 3 nebo 4 operandy (registry) |
Load/store pro pixely atd. | 2,3 nebo 4 prvky | × | × |
Přenosy mezi skalárními a vektorovými registry | ano | ano | ne |
Podpora float/double | 32bitový float(single) | single i double | single |
3. Pracovní registry používané instrukcemi NEON
Původní technologie NEON, o níž se v dnešním článku zajímáme a která byla implementovaná na 32bitových jádrech ARM, používala sadu třiceti dvou nových pracovních registrů, přičemž každý registr měl šířku 64 bitů. Tato sada byla oddělena od klasických celočíselných pracovních registrů, což samozřejmě zvýšilo možnosti překladače při optimalizacích kódu (bylo relativně snadné kombinovat skalární a vektorové operace – příkladem může být počitadlo smyčky realizované skalární hodnotou).
Nové „vektorové“ registry byly pojmenovány D0 až D31 (D = double, tedy dvojnásobná šířka oproti float/single), popř. mohly být vždy dva sousední registry spojeny do jednoho 128bitového registru (ty byly pojmenovány Q0 až Q15, Q = quad, tedy čtyřnásobná šířka oproti float/single). Pokud jádro kromě NEONu podporovalo i výše zmíněnou technologii VFP (konkrétně VFPv3 nebo VFPv4), byly registry D0 až D31 sdíleny mezi oběma jednotkami.
U jader ARMv8-A s instrukční sadou AArch64 (tedy poněkud nepřesně „u 64bitových ARMů“) došlo v této oblasti k poměrně podstatnému vylepšení, protože programátoři nově mají k dispozici 32 pracovních registrů, ovšem nyní se jedná o plnohodnotné 128bitové registry (což znamená dvojnásobné množství informací). Tyto registry jsou současně používány i při běžných matematických operacích s hodnotami uloženými v systému plovoucí řádové čárky a taktéž kryptografickým modulem. Došlo ještě k další změně – již nedochází k rozdělení jednoho registru pro typ double do dvou registrů pro hodnotu typu single/float, jako tomu bylo u VFP.
Nově je možné každý registr použít jako 128bitový vektor, pro uložení hodnoty double (spodních 64 bitů), uložení hodnoty typu single/float (spodních 32 bitů), popř. pro uložení hodnoty typu half float (spodních 16 bitů). Horní bity jsou při čtení ignorovány, při zápisu nulovány.
Jména „vektorových“ registrů jsou shrnuta v následující tabulce:
Jméno | Význam |
---|---|
v0..v31 | 128bitové registry |
d0..d31 | spodních 64 bitů registrů v0..v31, použito pro hodnoty typu double |
s0..s31 | spodních 32 bitů registrů v0..v31, použito pro hodnoty typu single/float |
h0..h31 | spodních 16 bitů registrů v0..v31, použito pro hodnoty typu half float |
4. Použitá terminologie: vector, lane, element
U technologie NEON se používá následující terminologie (která může být zpočátku poněkud matoucí):
- Vector vždy značí 64bitovou či 128bitovou část pracovního registru Vn, která je rozdělena na prvky.
- Element je prvek vektoru.
- Lane označuje index prvku vektoru. U mnoha operací se totiž kombinují prvky z různých vektorů, které mají shodný index, ovšem některé operace pro zabalení či rozbalení mohou používat odlišné indexy.
Indexy jednotlivých lanes se zvyšují směrem od nejnižšího bitu k bitu nejvyššímu. Pro pracovní registry V0 až V31 a použitý typ prvků mohou indexy nabývat těchto hodnot:
Vektor | Indexy jednotlivých lanes |
---|---|
128bitový registr | × |
2×64 bitů | 1, 0 |
4×32 bitů | 3, 2, 1, 0 |
8×16 bitů | 7, 6, ..0 |
16×8 bitů | 15..0 |
Víme již, že je možné namísto 128bitových vektorů používat i vektory 64bitové, což jsou vlastně registrové aliasy D0 až D31. Zde samozřejmě bude k dispozici jen polovina dostupných indexů:
Vektor | Indexy jednotlivých lanes |
---|---|
64bitový registr | × |
1×64 bitů | 0 |
2×32 bitů | 1, 0 |
4×16 bitů | 3, 2, 1, 0 |
8×8 bitů | 7, 6, ..0 |
5. Podporované formáty prvků vektorů
Instrukce NEON na 64bitových mikroprocesorech AArch64 podporují vektory s elementy (prvky) těchto typů:
Typ | Šířka | Poznámka |
---|---|---|
half | 16 bitů | hodnota s plovoucí řádovou čárkou, poloviční přesnost (jen převody) |
float | 32 bitů | hodnota s plovoucí řádovou čárkou, jednoduchá přesnost (výpočty) |
double | 64 bitů | hodnota s plovoucí řádovou čárkou, dvojitá přesnost (výpočty) |
int8 | 8 bitů | osmibitové celé číslo se znaménkem, obecné použití |
int16 | 16 bitů | 16bitové celé číslo se znaménkem, obecné použití |
int32 | 32 bitů | 32bitové celé číslo se znaménkem, obecné použití |
int64 | 64 bitů | 64bitové celé číslo se znaménkem, obecné použití |
uint8 | 8 bitů | osmibitové celé číslo bez znaménka, typicky barvová složka pixelu |
uint16 | 16 bitů | 16bitové celé číslo bez znaménka, typicky zvukový vzorek |
uint32 | 32 bitů | 32bitové celé číslo bez znaménka, obecné použití |
uint64 | 64 bitů | 64bitové celé číslo bez znaménka, obecné použití |
polynomial | 8 bitů | používán pro výpočty korekcí chyb atd. (dnes se jím nebudeme zabývat) |
polynomial | 16 bitů | používán pro výpočty korekcí chyb atd. (dnes se jím nebudeme zabývat) |
6. Instrukce určené pro zpracování skalárních dat
I přesto, že je technologie SIMD primárně určena pro provádění operací s prvky vektorů (tedy obecně řečeno s vektory), nabízí NEON i mnoho instrukcí pracujících s „pouhými“ skalárními daty. U těchto instrukcí se pracuje buď s celými 128bitovými hodnotami či s hodnotami 64bitovými, popř. „pouze“ s hodnotami 32bitovými. V prvním případě jsou zdrojové a cílové registry pojmenovány Vn, v případě druhém pak Dn (zde se tedy využije spodních 64 bitů původně 128bitového registru, u 32bitových operací jen spodních 32 bitů). Příkladem může být instrukce ADD, která může být prováděna na běžné aritmeticko-logické jednotce, kde bude pracovat s registry Wn či Xn. Rozšíření instrukční sady NEON nabízí stejnou instrukci, ovšem pro použití s registry Dn, které obsahují celé číslo (pro FP operace je určena instrukce FADD, což je vlastně historický způsob pojmenování).
Některé vybrané instrukce určené pro zpracování skalárních dat jsou vypsány v následující tabulce:
Instrukce | Poznámka |
---|---|
ABS | |
ADDP | |
CMEQ | |
CMEQ | |
CMGE | |
CMGE | |
CMGT | |
CMGT | |
CMHI | |
CMHS | |
CMLE | |
CMLT | |
CMTST | |
DUP | |
FABD | FPU |
FACGE | FPU |
FACGT | FPU |
FADDP | FPU |
FCMEQ | FPU |
FCMEQ | FPU |
FCMGE | FPU |
FCMGE | FPU |
FCMGT | FPU |
FCMGT | FPU |
FCMLE | FPU |
FCMLT | FPU |
FCVTAS | FPU |
FCVTAU | FPU |
FCVTMS | FPU |
FCVTMU | FPU |
FCVTNS | FPU |
FCVTNU | FPU |
FCVTPS | FPU |
FCVTPU | FPU |
FCVTXN | FPU |
FCVTZS | FPU |
FCVTZS | FPU |
FCVTZU | FPU |
FCVTZU | FPU |
FMAXNMP | FPU |
FMAXP | FPU |
FMINNMP | FPU |
FMINP | FPU |
FMLA | FPU |
FMLS | FPU |
FMUL | FPU |
FMULX | FPU |
FMULX | FPU |
FRECPE | FPU |
FRECPS | FPU |
FRECPX | FPU |
FRSQRTE | FPU |
FRSQRTS | FPU |
MOV | |
NEG | |
SCVTF | |
SCVTF | |
SHL | |
SLI | |
SQABS | výpočet se saturací, operand je celé číslo se znaménkem |
SQADD | výpočet se saturací, operand je celé číslo se znaménkem |
SQDMLAL | výpočet se saturací, operand je celé číslo se znaménkem |
SQDMLAL | výpočet se saturací, operand je celé číslo se znaménkem |
SQDMLSL | výpočet se saturací, operand je celé číslo se znaménkem |
SQDMLSL | výpočet se saturací, operand je celé číslo se znaménkem |
SQDMULH | výpočet se saturací, operand je celé číslo se znaménkem |
SQDMULH | výpočet se saturací, operand je celé číslo se znaménkem |
SQDMULL | výpočet se saturací, operand je celé číslo se znaménkem |
SQDMULL | výpočet se saturací, operand je celé číslo se znaménkem |
SQNEG | výpočet se saturací, operand je celé číslo se znaménkem |
SQRDMULH | výpočet se saturací, operand je celé číslo se znaménkem |
SQRDMULH | výpočet se saturací, operand je celé číslo se znaménkem |
SQRSHL | výpočet se saturací, operand je celé číslo se znaménkem |
SQRSHRN | výpočet se saturací, operand je celé číslo se znaménkem |
SQRSHRUN | výpočet se saturací, operand je celé číslo se znaménkem |
SQSHL | výpočet se saturací, operand je celé číslo se znaménkem |
SQSHL | výpočet se saturací, operand je celé číslo se znaménkem |
SQSHLU | výpočet se saturací, operand je celé číslo se znaménkem |
SQSHRN | výpočet se saturací, operand je celé číslo se znaménkem |
SQSHRUN | výpočet se saturací, operand je celé číslo se znaménkem |
SQSUB | výpočet se saturací, operand je celé číslo se znaménkem |
SQXTN | výpočet se saturací, operand je celé číslo se znaménkem |
SQXTUN | výpočet se saturací, operand je celé číslo se znaménkem |
SRI | |
SRSHL | |
SRSHR | |
SRSRA | |
SSHL | |
SSHR | |
SSRA | |
SUB | |
SUQADD | |
UCVTF | |
UCVTF | |
UQADD | výpočet se saturací, operand je celé číslo bez znaménka |
UQRSHL | výpočet se saturací, operand je celé číslo bez znaménka |
UQRSHRN | výpočet se saturací, operand je celé číslo bez znaménka |
UQSHL | výpočet se saturací, operand je celé číslo bez znaménka |
UQSHL | výpočet se saturací, operand je celé číslo bez znaménka |
UQSHRN | výpočet se saturací, operand je celé číslo bez znaménka |
UQSUB | výpočet se saturací, operand je celé číslo bez znaménka |
UQXTN | výpočet se saturací, operand je celé číslo bez znaménka |
URSHL | |
URSHR | |
URSRA | |
USHL | |
USHR | |
USQADD | |
USRA |
7. Formát instrukcí NEON, prefixy a suffixy u instrukcí
Pojmenování instrukcí technologie NEON je u AArch64 v porovnání s původní 32bitovou architekturou odlišné, s čímž se setkají především programátoři pracující s assemblerem nebo s debuggerem. Zejména došlo v první řadě k odstranění prefixového znaku V, protože způsob provádění instrukce je odvozen z typů operandů. Namísto toho se používají prefixy S, U, F a P s následujícím významem:
Prefix | Význam |
---|---|
S | signed |
U | unsigned |
F | floating point |
P | polynomial |
Přesná konfigurace vektorů, které se používají jako vstupní a výstupní operandy instrukcí, je určena suffixem přidaným za jméno příslušného registru, ať již se jedná o registr zdrojový či cílový. Suffix se od jména registru odděluje tečkou:
Suffix | Význam | Šířka vektoru |
---|---|---|
Vn.8B | 8 bitů × 8 | 64 bitů |
Vn.16B | 8 bitů × 16 | 128 bitů |
Vn.4H | 16 bitů či half × 4 | 64 bitů |
Vn.8H | 16 bitů či half × 8 | 128 bitů |
Vn.2S | 32 bitů či single/float × 2 | 64 bitů |
Vn.4S | 32 bitů či single/float × 4 | 128 bitů |
Vn.1D | 64 bitů či double × 1 | 64 bitů |
Vn.2D | 64 bitů či double × 2 | 128 bitů |
Zde můžeme vidět, že je skutečně nutné použít kombinaci prefixu a suffixu, aby bylo možné rozlišit například součet vektorů čtyř 32bitových celých čísel se znaménkem od součtu 32bitových celých čísel bez znaménka či čtyř hodnot typu single/float – ve všech případech mají vektory stejný počet prvků a prvky mají stejnou bitovou šířku, ale prováděná operace může být diametrálně odlišná.
Podívejme se nyní na praktické příklady – jak se výše uvedené názvy registrů používají v assembleru. Následující sekvence čtyř strojových instrukcí provádí vektorový součet pro vektory obsahující postupně celočíselné prvky 16×8 bitů, 8×16 bitů, 4×32 bitů a 2×64 bitů:
24: 4e208420 add v0.16b, v1.16b, v0.16b 48: 4e608420 add v0.8h, v1.8h, v0.8h 6c: 4ea08420 add v0.4s, v1.4s, v0.4s 90: 4ee08420 add v0.2d, v1.2d, v0.2d
Následuje součet vektorů obsahujících buď čtyři hodnoty typu float/single nebo dvě hodnoty typu double:
18: 4e20d420 fadd v0.4s, v1.4s, v0.4s 48: 4e60d420 fadd v0.2d, v1.2d, v0.2d
8. Konverze operandů (rozšíření, zmenšení), operace se saturací
Některé strojové instrukce, typicky základní aritmetické instrukce prováděné nad prvky vektorů, existují na moderních čipech AArch64 v několika variantách:
- V normální variantě se paralelně provádí operace nad jednotlivými prvky bez dalších úprav či konverzí. U této varianty je typ výsledného vektoru shodný s vektory zdrojovými. Právě tento typ instrukcí je výsledkem překladu kódu postaveného nad rozšířením GCC.
- U varianty označované slovem long (lengtening) se nejprve prvky vstupních vektorů zkonvertují na dvojnásobnou šířku (osm bitů → 16 bitů, 16 bitů → 32 bitů či 32 bitů → 64 bitů), provede se zvolená operace a výsledky se uloží do cílového vektoru. Vzhledem k tomu, že v tomto případě došlo k rozšíření prvků vektorů, je možné zpracovat vždy jen polovinu prvků, protože druhou polovinu by nebylo možné nikam uložit (cílový registr je vždy jen jediný). Z tohoto důvodu vždy existují dvě varianty každé instrukce, přičemž druhá varianta používá suffix 2 pro odlišení, zda se má pracovat s horní polovinou či dolní polovinou vektoru (což je podle mého názoru poněkud matoucí suffic).
- Další podporovaná varianta se jmenuje wide (widening) a dochází u ní ke kombinaci dvou zdrojových vektorů různých typů. Druhý zdrojový vektor obsahuje prvky s poloviční šířkou, které jsou opět zkonvertovány a následně je provedena zvolená operace. Opět platí, že každá instrukce existuje ve dvou variantách podle toho, kterou polovinu druhého zdrojového vektoru zpracovává.
- Opačná situace nastává u varianty označované narrow (narrowing), kde se nejprve provede zvolená operace a následně se prvky sníží na poloviční šířku.
- Další varianty rozlišují provedení operace se saturací, tj. celočíselné operace, u nichž nedojde k přetečení, ale výsledek se „zarazí“ na maximální nebo minimální hodnotě (nutné u všech multimediálních aplikací). V tomto případě se používají prefixySQ a UQ podle toho, zda prvky vstupních vektorů jsou hodnoty se znaménkem či bez znaménka.
- Posledním suffixem je znak P značící, že se zvolená operace nemá provést vždy mezi korespondujícími prvky dvou vstupních vektorů, ale vždy mezi dvěma sousedními prvky prvního zdrojového vektoru a posléze mezi stejnými páry druhého zdrojového vektoru. Opět se jedná o operaci, která je využitelná v mnohých multimediálních aplikacích.
9. Příklad různých variant instrukce ADD
Vzhledem k tomu, že u technologie NEON je možné používat vektory s prvky různých typů a navíc se při výpočtech může provádět konverze operandů (prodloužení, rozšíření atd. – viz též předchozí kapitolu), je celkový počet kombinací instrukcí velmi vysoký.
Můžeme si to ukázat na instrukci ADD, tj. instrukci, která má provést součet a kterou nalezneme na prakticky všech typech mikroprocesorů. Přitom se může jednat o součet dvou skalárů (různých typů) nebo o součet prvků vektorů, přičemž počet prvků a jejich typ se může lišit (připomeňme si, že můžeme použít „jen“ 64bitové vektory či naopak vektory 128bitové):
Instrukce | Význam/provedená operace |
---|---|
ADD Wd, Wn, Wm | skalární součet s 32bitovými operandy typu celé číslo (původní ARM) |
ADD Xd, Xn, Xm | skalární součet se 64bitovými operandy typu celé číslo (64bitová jádra) |
ADD Dd, Dn, Dm | skalární součet se 64bitovými operandy typu celé číslo, tentokrát s registry NEONu |
FADD Sd, Sn, Sm | skalární součet s FP operandy typu float/single (vzniklo před SIMD) |
FADD Dd, Dn, Dm | skalární součet s FP operandy typu double (vzniklo před SIMD) |
SQDD Vd.typ, Vn.typ, Vm.typ | skalární součet se saturací pro celá čísla se znaménkem |
UQDD Vd.typ, Vn.typ, Vm.typ | skalární součet se saturací pro celá čísla bez znaménka |
ADD Vd.8B, Vn.8B, Vm.8B | „vektorový“ součet osmi prvků typu byte |
ADD Vd.16B, Vn.16B, Vm.16B | „vektorový“ součet šestnácti prvků typu byte |
ADD Vd.4H, Vn.4H, Vm.4H | „vektorový“ součet čtyř prvků typu 16bit integer |
ADD Vd.8H, Vn.8H, Vm.8H | „vektorový“ součet osmi prvků typu 16bit integer |
ADD Vd.2S, Vn.2S, Vm.2S | „vektorový“ součet dvou prvků typu 32bit integer |
ADD Vd.4S, Vn.4S, Vm.4S | „vektorový“ součet čtyř prvků typu 32bit integer |
ADD Vd.2D, Vn.2D, Vm.2D | „vektorový“ součet dvou prvků typu 64bit integer |
FADD Vd.2S, Vn.2S, Vm.2S | „vektorový“ součet dvou prvků typu float/single |
FADD Vd.4S, Vn.4S, Vm.4S | „vektorový“ součet čtyř prvků typu float/single |
FADD Vd.2D, Vn.2D, Vm.2D | „vektorový“ součet dvou prvků typu double |
UADDL Vd.8H, Vn.8B, Vm.8B | „vektorový“ součet s rozšířením operandů bez znaménka (byte → 16bit integer) |
UADDL Vd.4S, Vn.4H, Vm.4H | „vektorový“ součet s rozšířením operandů bez znaménka (16bit integer → 32bit integer) |
UADDL Vd.2D, Vn.2S, Vm.2S | „vektorový“ součet s rozšířením operandů bez znaménka (32bit integer → 64bit integer) |
UADDL2 Vd.8H, Vn.8B, Vm.8B | jako první UADDL, ale pro druhou polovinu vektoru |
UADDL2 Vd.4S, Vn.4H, Vm.4H | jako první UADDL, ale pro druhou polovinu vektoru |
UADDL2 Vd.2D, Vn.2S, Vm.2S | jako první UADDL, ale pro druhou polovinu vektoru |
SADDL Vd.8H, Vn.8B, Vm.8B | „vektorový“ součet s rozšířením operandů se znaménkem (byte → 16bit integer) |
SADDL Vd.4S, Vn.4H, Vm.4H | „vektorový“ součet s rozšířením operandů se znaménkem (16bit integer → 32bit integer) |
SADDL Vd.2D, Vn.2S, Vm.2S | „vektorový“ součet s rozšířením operandů se znaménkem (32bit integer → 64bit integer) |
SADDL2 Vd.8H, Vn.8B, Vm.8B | jako první SADDL, ale pro druhou polovinu vektoru |
SADDL2 Vd.4S, Vn.4H, Vm.4H | jako první SADDL, ale pro druhou polovinu vektoru |
SADDL2 Vd.2D, Vn.2S, Vm.2S | jako první SADDL, ale pro druhou polovinu vektoru |
UADDW Vd.8H, Vn.8H, Vm.8B | „vektorový“ součet s rozšířením operandu bez znaménka (byte → 16bit integer) |
UADDW Vd.4S, Vn.4S, Vm.4H | „vektorový“ součet s rozšířením operandu bez znaménka (16bit integer → 32bit integer) |
UADDW Vd.2D, Vn.2D, Vm.2S | „vektorový“ součet s rozšířením operandu bez znaménka (32bit integer → 64bit integer) |
UADDW2 Vd.8H, Vn.8H, Vm.8B | jako první UADDW, ale pro druhou polovinu vektoru |
UADDW2 Vd.4S, Vn.4S, Vm.4H | jako první UADDW, ale pro druhou polovinu vektoru |
UADDW2 Vd.2D, Vn.2D, Vm.2S | jako první UADDW, ale pro druhou polovinu vektoru |
SADDW Vd.8H, Vn.8H, Vm.8B | „vektorový“ součet s rozšířením operandu se znaménkem (byte → 16bit integer) |
SADDW Vd.4S, Vn.4S, Vm.4H | „vektorový“ součet s rozšířením operandu se znaménkem (16bit integer → 32bit integer) |
SADDW Vd.2D, Vn.2D, Vm.2S | „vektorový“ součet s rozšířením operandu se znaménkem (32bit integer → 64bit integer) |
SADDW2 Vd.8H, Vn.8H, Vm.8B | jako první SADDW, ale pro druhou polovinu vektoru |
SADDW2 Vd.4S, Vn.4S, Vm.4H | jako první SADDW, ale pro druhou polovinu vektoru |
SADDW2 Vd.2D, Vn.2D, Vm.2S | jako první SADDW, ale pro druhou polovinu vektoru |
ADDP Vd.typ, Vn.typ, Vm.typ | součet sousedních párů prvků obou zdrojových vektorů |
FADDP Vd.typ, Vn.typ, Vm.typ | dtto, ale pro typy single a double |
10. Typy podporovaných vektorových instrukcí, aritmetické a logické instrukce
Instrukce zavedené v technologii NEON jsou navrženy takovým způsobem, aby je bylo možné použít při zpracování 1D signálů (filtry s konečnou a nekonečnou odezvou, FFT, DFT, DCT), při image processingu (tedy 2D matic), zpracování videa (motion vektory atd.), popř. v 3D grafice. Z tohoto důvodu je sada instrukcí poměrně velká a neomezuje se pouze na paralelní výpočet jednoduchých operací nad prvky vektorů:
Instrukce | Zpracování signálu | Image/video processing | Další použití |
---|---|---|---|
vektorový součet | obecně použitelný | změna velikosti, změna jasu | detekce kolizí |
maticový součet | FFT | rotace obrazu | |
vektorový rozdíl | obecně použitelný, FFT | změna jasu | |
maticový rozdíl | FIR | ||
násobení prvků vektorů | FIR | změna kontrastu | |
vektorový MAC | FIR | ||
násobení matic | FIR | ||
dělení prvků vektorů | IIR | ||
výpočet délky vektoru | |||
normalizace vektoru | 3D grafika | ||
absolutní hodnota | |||
skalární součin | 3D grafika | ||
vektorový součin | 3D grafika | ||
determinant matice | |||
inverzní matice | 3D grafika | ||
transpozice matice |
Základní aritmetické a logické instrukce prováděné s prvky vektorů jsou vypsány v následující tabulce. Povšimněte si, že některé instrukce existují ve variantách pro celočíselné operandy i operandy s plovoucí řádovou čárkou, u dalších instrukcí se navíc rozlišuje režim se saturací a instrukce pro násobení pracuje i pro typ polynomial:
Instrukce | Poznámka |
---|---|
ADD/FADD | součet (celočíselný, s FP hodnotami) |
SQADD/UQADD/ | součet se saturací |
SUB/FSUB | rozdíl |
SQSUB/UQSUB/ | rozdíl se saturací |
MUL/FMUL/PMUL | součin |
MLA/FMLA | multiply-accumulate (MAC) |
MLS/FMLS | multiply-subtract (podobné předchozímu, ale výsledek se odečte od akumulátoru) |
FDIV | podíl (jen pro single a double) |
UABD/SABD/FABD | absolutní hodnota rozdílu |
UABA/SABA | absolutní hodnota rozdílu se přičte s cílovému vektoru (akumulace) |
UMAX/SMAX/FMAX | maximální hodnota |
UMIN/SMIN/FMIN | minimální hodnota |
AND | logický součin bit po bitu |
EOR | logická operace XOR |
ORR | logický součet bit po bitu |
ORN | logický součet + negace |
11. Instrukce určené pro provedení jednoho kroku delší operace
Některé delší operace se rozdělují do série několika instrukcí a provádí se tak do jisté míry iterativně, přičemž v každé iteraci dochází ke zpřesnění výsledku. Zde záleží na potřebách programátora, jakou přesnost vyžaduje, resp. zda preferuje rychlý „odhad“ výsledků či přesný výpočet (otázkou zůstává, do jaké míry lze tyto požadavky vyjádřit ve vysokoúrovňových programovacích jazycích). U těchto instrukcí se mezivýsledky ukládají do speciálních registrů (konkrétně se jmenují FRECPS a FRSQRTS).
První takovou instrukcí je FRECPS. Tato instrukce vynásobí příslušné dvojice prvků vektorů, odečte tento výsledek od konstanty 2 a výsledek uloží do příslušných prvků cílového vektoru. Ve skutečnosti se jedná o jeden krok iterativního výpočtu:
xn+1 = xn (2-dxn)
Tento výpočet konverguje k hodnotě 1/d, ovšem jen ve chvíli, pokud je prvotní odhad výsledku x0 proveden instrukcí FRECPE.
Druhá instrukce se jmenuje FRSQRTS a provádí jeden krok iterativního výpočtu:
xn+1 = xn (3-dxn2)/2
Tento výpočet konverguje k hodnotě 1/√d, ovšem původní odhad mezivýsledku je nutné spočítat instrukcí FRSQRTE (E znamená „estimate“ zatímco S v předchozí instrukci znamená „step“).
12. Podpora v GCC pro základní vektorové operace
Na dva předchozí články [1] [2] o podpoře vektorových operací v rámci rozšíření GCC dnes navážeme. Ukážeme si, jak se demonstrační příklady, s nimiž jsme se v rámci obou článků seznámili, překládají pro architekturu AArch64 bez i s povolením SIMD operací. Začneme příkladem, v němž je definován vektor o délce šestnácti bajtů, jehož prvky jsou typu unsigned short int. Délka vektoru měřená v počtu prvků je tedy rovna:
length = 16 / sizeof(unsigned short int);
V příkladu vytvoříme dva vektory a sečteme je:
typedef unsigned short int v16us __attribute__((vector_size(16))); int main(void) { v16us x = { 1, 2, 3, 4, 5, 6, 7, 8 }; v16us y = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; v16us z = x + y; return 0; }
13. Překlad do strojového kódu se zákazem SIMD instrukcí
Nejprve se podívejme, jak se předchozí demonstrační příklad přeložil do strojového kódu mikroprocesorů AArch64, a to ve chvíli, kdy bylo zakázáno použití SIMD instrukcí. Pro tento účel je nutné překladač GCC C volat s přepínačem:
-march=armv8-a+nosimd
Výsledek (při zákazu optimalizací) by mohl vypadat následovně:
simd04_1.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 <main>: typedef unsigned short int v16us __attribute__((vector_size(16))); int main(void) { 0: d100c3ff sub sp, sp, #0x30 v16us x = { 1, 2, 3, 4, 5, 6, 7, 8 }; 4: 90000000 adrp x0, 0 <main> 8: 91000000 add x0, x0, #0x0 c: a9400400 ldp x0, x1, [x0] 10: a90207e0 stp x0, x1, [sp, #32] v16us y = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; 14: 90000000 adrp x0, 0 <main> 18: 91000000 add x0, x0, #0x0 1c: a9400400 ldp x0, x1, [x0] 20: a90107e0 stp x0, x1, [sp, #16] 24: f94013e0 ldr x0, [sp, #32] 28: f9400be1 ldr x1, [sp, #16] v16us z = x + y; 2c: ca010003 eor x3, x0, x1 30: 9200b821 and x1, x1, #0x7fff7fff7fff7fff 34: 9200b802 and x2, x0, #0x7fff7fff7fff7fff 38: 92018060 and x0, x3, #0x8000800080008000 3c: 8b010041 add x1, x2, x1 40: ca000024 eor x4, x1, x0 44: f94017e0 ldr x0, [sp, #40] 48: f9400fe1 ldr x1, [sp, #24] 4c: ca010003 eor x3, x0, x1 50: 9200b821 and x1, x1, #0x7fff7fff7fff7fff 54: 9200b802 and x2, x0, #0x7fff7fff7fff7fff 58: 92018060 and x0, x3, #0x8000800080008000 5c: 8b010041 add x1, x2, x1 60: ca000022 eor x2, x1, x0 64: d2800000 mov x0, #0x0 // #0 68: d2800001 mov x1, #0x0 // #0 6c: aa0403e0 mov x0, x4 70: aa0203e1 mov x1, x2 74: a90007e0 stp x0, x1, [sp] return 0; 78: 52800000 mov w0, #0x0 // #0 } 7c: 9100c3ff add sp, sp, #0x30 80: d65f03c0 ret
Za povšimnutí stojí způsob použití instrukce and pro naplnění cílového registru zvolenou hodnotou, zde konkrétně hodnotou představující čtveřici šestnáctibitových hodnot. Samotný výpočet byl eliminován, provádí se přímo dosazení konstant.
14. Překlad do strojového kódu s povolením SIMD instrukcí
Nyní si vyzkoušejme, jakým způsobem se stejný demonstrační příklad přeloží do strojového kódu čipů s architekturou AArch64 v případě, že jsou SIMD operace povoleny (což je výchozí nastavení):
simd04_2.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 <main>: typedef unsigned short int v16us __attribute__((vector_size(16))); int main(void) { 0: d100c3ff sub sp, sp, #0x30 v16us x = { 1, 2, 3, 4, 5, 6, 7, 8 }; 4: 90000000 adrp x0, 0 <main> 8: 3dc00000 ldr q0, [x0] c: 3d800be0 str q0, [sp, #32] v16us y = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; 10: 4f0787e0 movi v0.8h, #0xff 14: 3d8007e0 str q0, [sp, #16] v16us z = x + y; 18: 3dc00be1 ldr q1, [sp, #32] 1c: 3dc007e0 ldr q0, [sp, #16] 20: 4e608420 add v0.8h, v1.8h, v0.8h 24: 3d8003e0 str q0, [sp] return 0; 28: 52800000 mov w0, #0x0 // #0 } 2c: 9100c3ff add sp, sp, #0x30 30: d65f03c0 ret
Nejdůležitější část kódu je představována touto čtveřicí instrukcí:
18: 3dc00be1 ldr q1, [sp, #32] 1c: 3dc007e0 ldr q0, [sp, #16] 20: 4e608420 add v0.8h, v1.8h, v0.8h 24: 3d8003e0 str q0, [sp]
Zajímavé je, že instrukce ldr (načtení obsahu registru z paměti) i str (uložení obsahu registru do paměti) pracují s celým 128bitovým registrem, jakoby obsahoval skalární hodnotu (jméno registru je Q0). Z hlediska načítání a ukládání hodnoty je jedno, jak je registr chápán, ovšem samotný součet je prováděn (pochopitelně paralelně) po prvcích, konkrétně po osmi prvcích typu half – viz též zvýrazněnou instrukci součtu.
15. Základní vektorové operace s prvky vektorů typu float a double
Zbývá nám zjistit, jakým způsobem se přeloží kód s vektory, jejichž prvky jsou typu float a double. Opět se prozatím zaměříme pouze na základní „vektorovou“ operaci součtu:
#include <stdio.h> typedef float v16float __attribute__((vector_size(16))); void add16float(v16float x, v16float y, v16float * z) { *z = x + y; } typedef double v16double __attribute__((vector_size(16))); void add16double(v16double x, v16double y, v16double * z) { *z = x + y; } int main(void) { { v16float x = { 0, 1, 2, 3 }; v16float y = { 0.1, 0.1, 0.1, 0.1 }; v16float z; add16float(x, y, &z); int i; puts("vector of floats"); for (i = 0; i < sizeof(v16float) / sizeof(float); i++) { printf("%d %f\n", i, z[i]); } } putchar('\n'); { v16double x = { 0, 1 }; v16double y = { 0.1, 0.1 }; v16double z; add16double(x, y, &z); int i; puts("vector of doubles"); for (i = 0; i < sizeof(v16double) / sizeof(double); i++) { printf("%d %f\n", i, z[i]); } } return 0; }
16. Překlad do strojového kódu se zákazem SIMD instrukcí
Nyní jsme překladač donutili, aby výpočty skutečně realizoval ve vygenerovaném strojovém kódu, takže se podívejme na to, jak jsou tyto výpočty implementovány ve chvíli, kdy jsou SIMD operace (tedy NEON) zakázány.
Začneme funkcí pro součet dvou vektorů s prvky typu float:
void add16float(v16float x, v16float y, v16float * z) { 0: d10143ff sub sp, sp, #0x50 4: fd001be0 str d0, [sp, #48] 8: fd001fe1 str d1, [sp, #56] c: fd0013e1 str d1, [sp, #32] 10: fd0017e2 str d2, [sp, #40] 14: f9000fe0 str x0, [sp, #24] 18: bd4033e1 ldr s1, [sp, #48] 1c: bd4023e0 ldr s0, [sp, #32] *z = x + y; 20: 1e202823 fadd s3, s1, s0 24: bd4037e1 ldr s1, [sp, #52] 28: bd4027e0 ldr s0, [sp, #36] 2c: 1e202822 fadd s2, s1, s0 30: bd403be1 ldr s1, [sp, #56] 34: bd402be0 ldr s0, [sp, #40] 38: 1e202821 fadd s1, s1, s0 3c: bd403fe4 ldr s4, [sp, #60] 40: bd402fe0 ldr s0, [sp, #44] 44: 1e202880 fadd s0, s4, s0 48: bd0003e3 str s3, [sp] 4c: bd0007e2 str s2, [sp, #4] 50: bd000be1 str s1, [sp, #8] 54: bd000fe0 str s0, [sp, #12] 58: a94007e0 ldp x0, x1, [sp] 5c: a90407e0 stp x0, x1, [sp, #64] 60: f9400fe2 ldr x2, [sp, #24] 64: a94407e0 ldp x0, x1, [sp, #64] 68: a9000440 stp x0, x1, [x2] } 6c: d503201f nop 70: 910143ff add sp, sp, #0x50 74: d65f03c0 ret
Z kódu je patrné, že se nejdříve všechny prvky (tedy jednotlivé prvky) vektorů uloží do pracovních registrů Sx, konkrétně do registrů odpovídajících datovému typu single/float. Posléze se provede čtveřice součtů s uložením výsledků zpět do registrů Sx, zde konkrétně do registrů S0 až S3. Tyto registry jsou posléze uloženy na zásobník, kde je očekává volající funkce.
Nyní se podívejme na funkci pro součet vektorů s prvky typu double:
{ 78: d10143ff sub sp, sp, #0x50 7c: fd001be0 str d0, [sp, #48] 80: fd001fe1 str d1, [sp, #56] 84: fd0013e1 str d1, [sp, #32] 88: fd0017e2 str d2, [sp, #40] 8c: f9000fe0 str x0, [sp, #24] 90: fd401be1 ldr d1, [sp, #48] 94: fd4013e0 ldr d0, [sp, #32] *z = x + y; 98: 1e602821 fadd d1, d1, d0 9c: fd401fe2 ldr d2, [sp, #56] a0: fd4017e0 ldr d0, [sp, #40] a4: 1e602840 fadd d0, d2, d0 a8: fd0003e1 str d1, [sp] ac: fd0007e0 str d0, [sp, #8] b0: a94007e0 ldp x0, x1, [sp] b4: a90407e0 stp x0, x1, [sp, #64] b8: f9400fe2 ldr x2, [sp, #24] bc: a94407e0 ldp x0, x1, [sp, #64] c0: a9000440 stp x0, x1, [x2] } c4: d503201f nop c8: 910143ff add sp, sp, #0x50 cc: d65f03c0 ret
Situace je zde obdobná, ovšem používají se registry Dx a součet je realizován dvěma instrukcemi fadd a nikoli instrukcemi čtyřmi.
17. Překlad do strojového kódu s povolením SIMD instrukcí
Po povolení překladu do strojového kódu s povolením SIMD instrukcí (zde konkrétně z rozšíření NEON) dostaneme triviální funkci s jedinou vektorovou instrukcí fadd pracující s registry v0 a v1 (tedy aliasy pro q0 a q1):
void add16float(v16float x, v16float y, v16float * z) { 0: d100c3ff sub sp, sp, #0x30 4: 3d800be0 str q0, [sp, #32] 8: 3d8007e1 str q1, [sp, #16] c: f90007e0 str x0, [sp, #8] *z = x + y; 10: 3dc00be1 ldr q1, [sp, #32] 14: 3dc007e0 ldr q0, [sp, #16] 18: 4e20d420 fadd v0.4s, v1.4s, v0.4s 1c: f94007e0 ldr x0, [sp, #8] 20: 3d800000 str q0, [x0] } 24: d503201f nop 28: 9100c3ff add sp, sp, #0x30 2c: d65f03c0 ret
Prakticky totožným způsobem (až na instrukci pro součet) je realizován součet vektorů s prvky typu double:
void add16double(v16double x, v16double y, v16double * z) { 30: d100c3ff sub sp, sp, #0x30 34: 3d800be0 str q0, [sp, #32] 38: 3d8007e1 str q1, [sp, #16] 3c: f90007e0 str x0, [sp, #8] *z = x + y; 40: 3dc00be1 ldr q1, [sp, #32] 44: 3dc007e0 ldr q0, [sp, #16] 48: 4e60d420 fadd v0.2d, v1.2d, v0.2d 4c: f94007e0 ldr x0, [sp, #8] 50: 3d800000 str q0, [x0] } 54: d503201f nop 58: 9100c3ff add sp, sp, #0x30 5c: d65f03c0 ret
18. Znovuzrození datového typu single/float?
Datový typ single/float byl implementován už na matematickém koprocesoru Intel 8087, ovšem jen částečně, protože všechny výpočty ve skutečnosti probíhaly s rozšířenou přesností a typy float i double byly použity až při závěrečných konverzích před uložením výsledků. Zdálo se tedy, že výhoda typu float spočívá pouze při práci s velkými poli, kdy dochází k ušetření paměti, ale nikoli ke zrychlení výpočtů. SIMD operace ovšem mohou probíhat výrazně rychleji u vektorů s větším počtem prvků – teoreticky lze dosáhnout více než dvojnásobné rychlosti u typů float v porovnání s double, takže se v mnoha (novějších) modelech už od začátku počítá s jednoduchou přesností (a v případě neuronových sítí atd. ještě s poloviční přesností). To již vyžaduje větší znalosti vlastností numerických hodnot s plovoucí řádovou čárkou/tečkou, což je téma, kterému jsme se věnovali zde.
19. Repositář s demonstračními příklady
Demonstrační příklady napsané v jazyku C, které jsou určené pro překlad pomocí překladače GCC C, byly uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/presentations. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již velmi rozsáhlý) repositář:
Soubory vzniklé překladem z jazyka C do assembleru procesorů x86–64:
# | Příklad | Stručný popis | Adresa |
---|---|---|---|
1 | simd04_1.lst | překlad zdrojového kódu simd04_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04_1.lst |
2 | simd04_2.lst | překlad zdrojového kódu simd04_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04_2.lst |
3 | simd04B1.lst | překlad zdrojového kódu simd04B1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04B1.lst |
4 | simd04B2.lst | překlad zdrojového kódu simd04B2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04B2.lst |
5 | simd07_1.lst | překlad zdrojového kódu simd07_1.c s přepínači -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd07_1.lst |
6 | simd07_2.lst | překlad zdrojového kódu simd07_2.c s přepínači -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd07_2.lst |
7 | simd08_1.lst | překlad zdrojového kódu simd08_1.c s přepínači -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd08_1.lst |
8 | simd08_2.lst | překlad zdrojového kódu simd08_2.c s přepínači -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd08_2.lst |
9 | simd12_1.lst | překlad zdrojového kódu simd12_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd12_1.lst |
10 | simd12_2.lst | překlad zdrojového kódu simd12_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd12_2.lst |
11 | simd13_1.lst | překlad zdrojového kódu simd13_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_1.lst |
12 | simd13_2.lst | překlad zdrojového kódu simd13_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_2.lst |
13 | simd13_3.lst | překlad zdrojového kódu simd13_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_3.lst |
14 | simd13_4.lst | překlad zdrojového kódu simd13_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_4.lst |
15 | simd14_1.lst | překlad zdrojového kódu simd14_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_1.lst |
16 | simd14_2.lst | překlad zdrojového kódu simd14_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_2.lst |
17 | simd14_3.lst | překlad zdrojového kódu simd14_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_3.lst |
18 | simd14_4.lst | překlad zdrojového kódu simd14_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_4.lst |
19 | simd15_1.lst | překlad zdrojového kódu simd15_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_1.lst |
20 | simd15_2.lst | překlad zdrojového kódu simd15_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_2.lst |
21 | simd15_3.lst | překlad zdrojového kódu simd15_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_3.lst |
22 | simd15_4.lst | překlad zdrojového kódu simd15_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_4.lst |
23 | simd16_1.lst | překlad zdrojového kódu simd16_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_1.lst |
24 | simd16_2.lst | překlad zdrojového kódu simd16_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_2.lst |
25 | simd16_3.lst | překlad zdrojového kódu simd16_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_3.lst |
26 | simd16_4.lst | překlad zdrojového kódu simd16_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_4.lst |
27 | simd17_1.lst | překlad zdrojového kódu simd17_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_1.lst |
28 | simd17_2.lst | překlad zdrojového kódu simd17_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_2.lst |
29 | simd17_3.lst | překlad zdrojového kódu simd17_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_3.lst |
30 | simd17_4.lst | překlad zdrojového kódu simd17_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_4.lst |
31 | simd18_1.lst | překlad zdrojového kódu simd18_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_1.lst |
32 | simd18_2.lst | překlad zdrojového kódu simd18_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_2.lst |
33 | simd18_3.lst | překlad zdrojového kódu simd18_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_3.lst |
34 | simd18_4.lst | překlad zdrojového kódu simd18_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_4.lst |
Soubory vzniklé překladem z jazyka C do assembleru procesorů ARMv8:
# | Příklad | Stručný popis | Adresa |
---|---|---|---|
1 | simd04_1.lst | překlad zdrojového kódu simd04_1.c s přepínači -O0 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04_1.lst |
2 | simd04_2.lst | překlad zdrojového kódu simd04_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04_2.lst |
3 | simd04B1.lst | překlad zdrojového kódu simd04B1.c s přepínači -O0 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04B1.lst |
4 | simd04B2.lst | překlad zdrojového kódu simd04B2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04B2.lst |
5 | simd07_1.lst | překlad zdrojového kódu simd07_1.c s přepínači -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd07_1.lst |
6 | simd07_2.lst | překlad zdrojového kódu simd07_2.c s přepínači -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd07_2.lst |
7 | simd08_1.lst | překlad zdrojového kódu simd08_1.c s přepínači -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd08_1.lst |
8 | simd08_2.lst | překlad zdrojového kódu simd08_2.c s přepínači -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd08_2.lst |
9 | simd12_1.lst | překlad zdrojového kódu simd12_1.c s přepínači -O0 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd12_1.lst |
10 | simd12_2.lst | překlad zdrojového kódu simd12_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd12_2.lst |
11 | simd13_1.lst | překlad zdrojového kódu simd13_1.c s přepínači -O0 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_1.lst |
12 | simd13_2.lst | překlad zdrojového kódu simd13_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_2.lst |
13 | simd13_3.lst | překlad zdrojového kódu simd13_3.c s přepínači -O3 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_3.lst |
14 | simd13_4.lst | překlad zdrojového kódu simd13_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_4.lst |
15 | simd14_1.lst | překlad zdrojového kódu simd14_1.c s přepínači -O0 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_1.lst |
16 | simd14_2.lst | překlad zdrojového kódu simd14_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_2.lst |
17 | simd14_3.lst | překlad zdrojového kódu simd14_3.c s přepínači -O3 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_3.lst |
18 | simd14_4.lst | překlad zdrojového kódu simd14_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_4.lst |
19 | simd15_1.lst | překlad zdrojového kódu simd15_1.c s přepínači -O0 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_1.lst |
20 | simd15_2.lst | překlad zdrojového kódu simd15_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_2.lst |
21 | simd15_3.lst | překlad zdrojového kódu simd15_3.c s přepínači -O3 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_3.lst |
22 | simd15_4.lst | překlad zdrojového kódu simd15_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_4.lst |
23 | simd16_1.lst | překlad zdrojového kódu simd16_1.c s přepínači -O0 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_1.lst |
24 | simd16_2.lst | překlad zdrojového kódu simd16_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_2.lst |
25 | simd16_3.lst | překlad zdrojového kódu simd16_3.c s přepínači -O3 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_3.lst |
26 | simd16_4.lst | překlad zdrojového kódu simd16_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_4.lst |
27 | simd17_1.lst | překlad zdrojového kódu simd17_1.c s přepínači -O0 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_1.lst |
28 | simd17_2.lst | překlad zdrojového kódu simd17_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_2.lst |
29 | simd17_3.lst | překlad zdrojového kódu simd17_3.c s přepínači -O3 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_3.lst |
30 | simd17_4.lst | překlad zdrojového kódu simd17_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_4.lst |
31 | simd18_1.lst | překlad zdrojového kódu simd18_1.c s přepínači -O0 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_1.lst |
32 | simd18_2.lst | překlad zdrojového kódu simd18_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_2.lst |
33 | simd18_3.lst | překlad zdrojového kódu simd18_3.c s přepínači -O3 -march=armv8-a+nosimd -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_3.lst |
34 | simd18_4.lst | překlad zdrojového kódu simd18_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_4.lst |
20. Odkazy na Internetu
- GCC documentation: Extensions to the C Language Family
https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions - GCC documentation: Using Vector Instructions through Built-in Functions
https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html - SSE (Streaming SIMD Extentions)
http://www.songho.ca/misc/sse/sse.html - Timothy A. Chagnon: SSE and SSE2
http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf - Intel corporation: Extending the Worldr's Most Popular Processor Architecture
http://download.intel.com/technology/architecture/new-instructions-paper.pdf - SIMD architectures:
http://arstechnica.com/old/content/2000/03/simd.ars/ - Tour of the Black Holes of Computing!: Floating Point
http://www.cs.hmc.edu/~geoff/classes/hmc.cs105…/slides/class02_floats.ppt - 3Dnow! Technology Manual
AMD Inc., 2000 - Intel MMXTM Technology Overview
Intel corporation, 1996 - MultiMedia eXtensions
http://softpixel.com/~cwright/programming/simd/mmx.phpi - AMD K5 („K5“ / „5k86“)
http://www.pcguide.com/ref/cpu/fam/g5K5-c.html - Sixth Generation Processors
http://www.pcguide.com/ref/cpu/fam/g6.htm - Great Microprocessors of the Past and Present
http://www.cpushack.com/CPU/cpu1.html - Very long instruction word (Wikipedia)
http://en.wikipedia.org/wiki/Very_long_instruction_word - CPU design (Wikipedia)
http://en.wikipedia.org/wiki/CPU_design - Bulldozer (microarchitecture)
https://en.wikipedia.org/wiki/Bulldozer_(microarchitecture) - SIMD Instructions Considered Harmful
https://www.sigarch.org/simd-instructions-considered-harmful/ - GCC Compiler Intrinsics
https://iq.opengenus.org/gcc-compiler-intrinsics/ - Scalable_Vector_Extension_(SVE)
https://en.wikipedia.org/wiki/AArch64#Scalable_Vector_Extension_(SVE) - FADD/FADDP/FIADD — Add
https://www.felixcloutier.com/x86/fadd:faddp:fiadd - ADDPS — Add Packed Single-Precision Floating-Point Values
https://www.felixcloutier.com/x86/addps - ADDPD — Add Packed Double-Precision Floating-Point Values
https://www.felixcloutier.com/x86/addpd - FDIV/FDIVP/FIDIV — Divide
https://www.felixcloutier.com/x86/fdiv:fdivp:fidiv - IDIV — Signed Divide
https://www.felixcloutier.com/x86/idiv - PADDB/PADDW/PADDD/PADDQ — Add Packed Integers
https://www.felixcloutier.com/x86/paddb:paddw:paddd:paddq - PSUBB/PSUBW/PSUBD — Subtract Packed Integers
https://www.felixcloutier.com/x86/psubb:psubw:psubd - PMULLW — Multiply Packed Signed Integers and Store Low Result
https://www.felixcloutier.com/x86/pmullw - PUNPCKLBW/PUNPCKLWD/PUNPCKLDQ/PUNPCKLQDQ — Unpack Low Data
https://www.felixcloutier.com/x86/punpcklbw:punpcklwd:punpckldq:punpcklqdq - PUNPCKHBW/PUNPCKHWD/PUNPCKHDQ/PUNPCKHQDQ — Unpack High Data
https://www.felixcloutier.com/x86/punpckhbw:punpckhwd:punpckhdq:punpckhqdq - PACKUSWB — Pack with Unsigned Saturation
https://www.felixcloutier.com/x86/packuswb - ADDPS — Add Packed Single-Precision Floating-Point Values
https://www.felixcloutier.com/x86/addps - SUBPS — Subtract Packed Single-Precision Floating-Point Values
https://www.felixcloutier.com/x86/subps - MULPS — Multiply Packed Single-Precision Floating-Point Values
https://www.felixcloutier.com/x86/mulps - DIVPS — Divide Packed Single-Precision Floating-Point Values
https://www.felixcloutier.com/x86/divps - CBW/CWDE/CDQE — Convert Byte to Word/Convert Word to Doubleword/Convert Doubleword to Quadword
https://www.felixcloutier.com/x86/cbw:cwde:cdqe - PAND — Logical AND
https://www.felixcloutier.com/x86/pand - POR — Bitwise Logical OR
https://www.felixcloutier.com/x86/por - PXOR — Logical Exclusive OR
https://www.felixcloutier.com/x86/pxor - Improve the Multimedia User Experience
https://www.arm.com/technologies/neon - NEON Technology (stránky ARM)
https://developer.arm.com/technologies/neon - SIMD Assembly Tutorial: ARM NEON – Xiph.org
https://people.xiph.org/~tterribe/daala/neon_tutorial.pdf - Ne10
http://projectne10.github.io/Ne10/ - NEON and Floating-Point architecture
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/BABIGHEB.html - An Introduction to ARM NEON
http://peterdn.com/post/an-introduction-to-ARM-NEON.aspx - ARM NEON Intrinsics Reference
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0073a/IHI0073A_arm_neon_intrinsics_ref.pdf - Arm Neon Intrinsics vs hand assembly
https://stackoverflow.com/questions/9828567/arm-neon-intrinsics-vs-hand-assembly - ARM NEON Optimization. An Example
http://hilbert-space.de/?p=22 - AArch64 NEON instruction format
https://developer.arm.com/docs/den0024/latest/7-aarch64-floating-point-and-neon/73-aarch64-neon-instruction-format - ARM SIMD instructions
https://developer.arm.com/documentation/dht0002/a/Introducing-NEON/What-is-SIMD-/ARM-SIMD-instructions - Learn the architecture – Migrate Neon to SVE Version 1.0
https://developer.arm.com/documentation/102131/0100/?lang=en - 1.2.2. Comparison between NEON technology and other SIMD solutions
https://developer.arm.com/documentation/den0018/a/Introduction/Comparison-between-ARM-NEON-technology-and-other-implementations/Comparison-between-NEON-technology-and-other-SIMD-solutions?lang=en - NEON Programmer’s Guide
https://documentation-service.arm.com/static/63299276e68c6809a6b41308 - Brain Floating Point – nový formát uložení čísel pro strojové učení a chytrá čidla
https://www.root.cz/clanky/brain-floating-point-ndash-novy-format-ulozeni-cisel-pro-strojove-uceni-a-chytra-cidla/