Hlavní navigace

Podpora SIMD (vektorových) instrukcí na RISCových procesorech

18. 10. 2022
Doba čtení: 47 minut

Sdílet

 Autor: Depositphotos
SIMD operace, s jejichž některými variantami určenými pro platformu x86–64 jsme se seznámili minule, se používají i na RISCových procesorech. Dnes se seznámíme s rozšířením NEON pro procesory ARM.

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 floatdouble

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

20. Odkazy na Internetu

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ů
Poznámka: v předchozí tabulce znamená sloupec „Šířka vektorů“ bitovou šířku „vektorové ALU“, tedy vlastně, kolik bitů je možné zpracovat v rámci jednoho taktu. Pokud se mají zpracovávat vektory o šířce 128 bitů (což je na úrovni strojového kódu umožněno), je u starších jader takový vektor ve skutečnosti zpracován ve dvou taktech, vždy po 64 bitech, což znamená, že teoretické urychlení není tak velké, jak by se mohlo při čtení dalšího textu zdát.

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.

Poznámka: Na tomto místě je vhodné zdůraznit, že i přesto, že se v názvu technologie VFP používá termín „vector“, nejednalo se ve skutečnosti o implementaci plnohodnotných SIMD operací, protože se prvky vektorů zpracovávaly postupně, tedy sekvenčně (stále se však jednalo o vylepšení, protože se ušetřilo načtení instrukce a její dekódování). Z tohoto důvodu byl „vektorový režim“ VFP poměrně rychle nahrazen novou technologií nazvanou NEON označovanou též Advanced SIMD, která nás zajímá v tomto článku. Dnes se s VFP můžeme na některých ARMovských jádrech setkat, další jádra pak podporují pouze VFPLite, kde však každá operace trvá zhruba deset strojových cyklů!

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).

Poznámka: skutečná míra paralelnosti SIMD operací se na různých ARM jádrech lišila. Například na Cortex-A8 se sice stále daly provádět operace se 128bitovými registry obsahujícími prvky vektorů (viz navazující kapitoly), ovšem ve skutečnosti se v daný okamžik zpracovávalo jen 64 bitů, tj. každá operace se musela provádět dvakrát. Naproti tomu na Cortex-15 se již zpracovávaly celé 128bitové registry.

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):

  1. Bez jednotky NEON, bez jednotky VFP
  2. S jednotkou NEON, ale bez jednotky VFP
  3. Bez jednotky NEON, ale s jednotkou VFP
  4. 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).

Poznámka: jen připomínám, že VFP sice taktéž podporuje vektorové operace, ale nikoli v takové efektivitě jako NEON. Proto se VFP dnes používá pro skalární FP operace a někdy se označuje jménem Floating Point Unit neboli zkráceně FPU.

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 D0D31 (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 Q0Q15, 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 D0D31 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
Poznámka: datový typ half float o šířce pouhých šestnácti bitů je podporován jen několika instrukcemi. Nejedná se totiž o formát určený primárně pro zpracování hodnot, ale „pouze“ o formát pro načtení či naopak uložení výsledků. S tímto formátem se můžeme setkat u grafických akcelerátorů či v OpenCL. Zde má použití half float svůj význam, protože u některých algoritmů se úzkým hrdlem výpočtu stává přenosová rychlost sběrnice, rychlost přístupu do operační paměti či výpadky cache (a u pole prvků typu half float bude výpadků méně, než u prvků typu single/float či dokonce double). Viz též článek Brain Floating Point – nový formát uložení čísel pro strojové učení a chytrá čidla, v němž se zmiňujeme o několika „half“ formátech.

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í):

  1. Vector vždy značí 64bitovou či 128bitovou část pracovního registru Vn, která je rozdělena na prvky.
  2. Element je prvek vektoru.
  3. 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 V0V31 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 D0D31. 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
Poznámka: při popisu dnešních demonstračních příkladů se bez lanes obejdeme, ovšem příště už je budeme používat.

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)
Poznámka: některé typy (formáty) jsou dostupné jen pro NEON implementovaný na mikroprocesorech s architekturou AArch64. Týká se to zejména použití vektorů s elementy typu double.

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
Poznámka: zde (jedná se o GNU utils) není prefixový znak použit, takže se jedná o celočíselné operace.

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
Poznámka: povšimněte si, že operační kódy všech vektorových instrukcí mají shodnou šířku čtyř bajtů, stejně jako všechny ostatní „skalární“ instrukce.

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:

  • 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
Poznámka: jak uvidíme dále, je kódování instrukcí navrženo takovým způsobem, že všechny instrukce stále mohou mít konstantní délku čtyři bajty.

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
Poznámka: prefix F značí operace s operandy s plovoucí řádovou čárkou, prefix P pak typ „polynomial“. V některých případech se odlišuje i prefix S a U, tedy „signed“ a „unsigned“ (zde pro celočíselné hodnoty, protože u hodnot s plovoucí řádovou čárkou dnes používáme znaménko vždy).

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“).

Poznámka: příklady použití těchto instrukcí budou uvedeny později v článku o intrinsic.

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);
Poznámka: na této konkrétní architektuře se bude jednat o osmiprvkové vektory.

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
Poznámka: teoreticky je možné zakázat i FP operace (tedy zcela vynechat matematický koprocesor), což však u tohoto konkrétního demonstračního příkladu nehraje velkou roli.

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.

Poznámka: podobnou eliminaci celého výpočtu jsme mohli vidět i u architektury x86–64 v případě, že výpočet proběhl se známými hodnotami a přímo ve funkci main.

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.

Poznámka: jména Q0 a V0 odkazují na totožný 128bitový registr.

15. Základní vektorové operace s prvky vektorů typu floatdouble

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ů S0S3. 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.

Poznámka: povšimněte si, jak je díky velkému dostupnému množství pracovních registrů výsledný strojový kód relativně jednoduchý a přímočarý, a to i přesto, že ABI v tomto případě vyžaduje přenos parametrů přes zásobník.

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
Poznámka: povšimněte si, jak je zapsáno, že se vektorové registry používají s hodnotami typu single/float.

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
Poznámka: strojový kód se skutečně odlišuje pouze v jediné instrukci – fadd.

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 floatdouble 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/pre­sentations. 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ář:

bitcoin_skoleni

# Příklad Stručný popis Adresa
1 simd01.c vektor celých čísel typu short int https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd01.c
2 simd02.c ukázka použití vektorů s celočíselnými typy bez znaménka https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd02.c
3 simd03.c ukázka použití vektorů s celočíselnými typy se znaménkem https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd03.c
4 simd04.c paralelní součet celočíselných prvků vektorů https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd04.c
5 simd04B.c úprava pro další datové typy https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd04B.c
6 simd05.c přístup k jednotlivým prvkům vektorů https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd05.c
7 simd05B.c korektnější výpočet počtu prvků vektoru https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd05B.c
8 simd05C.c definice typu vektoru https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd05C.c
9 simd06.c vektor čísel s plovoucí řádovou čárkou https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd06.c
10 simd07.c paralelní součet prvků vektorů (typ float) https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd07.c
11 simd08.c paralelní součet prvků vektorů (typ double) https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd08.c
12 simd09.c překročení délky vektoru https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd09.c
13 simd10.c přístup k jednotlivým prvkům vektorů https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd10.c
14 simd11.c překročení délky vektoru https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd11.c
15 simd12.c dlouhý vektor s 256 bajty https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd12.c
       
16 simd13.c operace součtu pro vektory s celočíselnými prvky rozličné bitové šířky bez znaménka https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd13.c
17 simd14.c operace součtu pro vektory s celočíselnými prvky rozličné bitové šířky se znaménkem https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd14.c
18 simd15.c operace součtu pro vektory s prvky rozličné bitové šířky s plovoucí řádovou čárkou https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd15.c
19 simd16.c operace součtu pro dlouhé vektory s prvky rozličné bitové šířky s plovoucí řádovou čárkou https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd16.c
20 simd17.c všechny podporované binární operace nad vektory s celočíselnými prvky se znaménkem https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd17.c
21 simd18.c všechny podporované binární operace nad vektory s prvky typu float https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd18.c
       
22 Makefile Makefile pro překlad demonstračních příkladů https://github.com/tisnik/pre­sentations/blob/master/SIM­D/Makefile

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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/simd04B1.lst
4 simd04B2.lst překlad zdrojového kódu simd04B2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/simd04B1.lst
4 simd04B2.lst překlad zdrojového kódu simd04B2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/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/pre­sentations/blob/master/SIM­D/simd18_4.lst

20. Odkazy na Internetu

  1. GCC documentation: Extensions to the C Language Family
    https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions
  2. GCC documentation: Using Vector Instructions through Built-in Functions
    https://gcc.gnu.org/online­docs/gcc/Vector-Extensions.html
  3. SSE (Streaming SIMD Extentions)
    http://www.songho.ca/misc/sse/sse­.html
  4. Timothy A. Chagnon: SSE and SSE2
    http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf
  5. Intel corporation: Extending the Worldr's Most Popular Processor Architecture
    http://download.intel.com/techno­logy/architecture/new-instructions-paper.pdf
  6. SIMD architectures:
    http://arstechnica.com/ol­d/content/2000/03/simd.ar­s/
  7. Tour of the Black Holes of Computing!: Floating Point
    http://www.cs.hmc.edu/~ge­off/classes/hmc.cs105…/sli­des/class02_floats.ppt
  8. 3Dnow! Technology Manual
    AMD Inc., 2000
  9. Intel MMXTM Technology Overview
    Intel corporation, 1996
  10. MultiMedia eXtensions
    http://softpixel.com/~cwrig­ht/programming/simd/mmx.phpi
  11. AMD K5 („K5“ / „5k86“)
    http://www.pcguide.com/ref/cpu/fam/g5K5-c.html
  12. Sixth Generation Processors
    http://www.pcguide.com/ref/cpu/fam/g6­.htm
  13. Great Microprocessors of the Past and Present
    http://www.cpushack.com/CPU/cpu1.html
  14. Very long instruction word (Wikipedia)
    http://en.wikipedia.org/wi­ki/Very_long_instruction_word
  15. CPU design (Wikipedia)
    http://en.wikipedia.org/wi­ki/CPU_design
  16. Bulldozer (microarchitecture)
    https://en.wikipedia.org/wi­ki/Bulldozer_(microarchitec­ture)
  17. SIMD Instructions Considered Harmful
    https://www.sigarch.org/simd-instructions-considered-harmful/
  18. GCC Compiler Intrinsics
    https://iq.opengenus.org/gcc-compiler-intrinsics/
  19. Scalable_Vector_Extension_(SVE)
    https://en.wikipedia.org/wi­ki/AArch64#Scalable_Vector_Ex­tension_(SVE)
  20. FADD/FADDP/FIADD — Add
    https://www.felixcloutier­.com/x86/fadd:faddp:fiadd
  21. ADDPS — Add Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/addps
  22. ADDPD — Add Packed Double-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/addpd
  23. FDIV/FDIVP/FIDIV — Divide
    https://www.felixcloutier­.com/x86/fdiv:fdivp:fidiv
  24. IDIV — Signed Divide
    https://www.felixcloutier­.com/x86/idiv
  25. PADDB/PADDW/PADDD/PADDQ — Add Packed Integers
    https://www.felixcloutier­.com/x86/paddb:paddw:paddd:pad­dq
  26. PSUBB/PSUBW/PSUBD — Subtract Packed Integers
    https://www.felixcloutier­.com/x86/psubb:psubw:psubd
  27. PMULLW — Multiply Packed Signed Integers and Store Low Result
    https://www.felixcloutier­.com/x86/pmullw
  28. PUNPCKLBW/PUNPCKLWD/PUNPCKLDQ/PUN­PCKLQDQ — Unpack Low Data
    https://www.felixcloutier­.com/x86/punpcklbw:punpcklwd:pun­pckldq:punpcklqdq
  29. PUNPCKHBW/PUNPCKHWD/PUNPCKHDQ/PUN­PCKHQDQ — Unpack High Data
    https://www.felixcloutier­.com/x86/punpckhbw:punpckhwd:pun­pckhdq:punpckhqdq
  30. PACKUSWB — Pack with Unsigned Saturation
    https://www.felixcloutier­.com/x86/packuswb
  31. ADDPS — Add Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/addps
  32. SUBPS — Subtract Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/subps
  33. MULPS — Multiply Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/mulps
  34. DIVPS — Divide Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/divps
  35. CBW/CWDE/CDQE — Convert Byte to Word/Convert Word to Doubleword/Convert Doubleword to Quadword
    https://www.felixcloutier­.com/x86/cbw:cwde:cdqe
  36. PAND — Logical AND
    https://www.felixcloutier­.com/x86/pand
  37. POR — Bitwise Logical OR
    https://www.felixcloutier.com/x86/por
  38. PXOR — Logical Exclusive OR
    https://www.felixcloutier­.com/x86/pxor
  39. Improve the Multimedia User Experience
    https://www.arm.com/technologies/neon
  40. NEON Technology (stránky ARM)
    https://developer.arm.com/techno­logies/neon
  41. SIMD Assembly Tutorial: ARM NEON – Xiph.org
    https://people.xiph.org/~tte­rribe/daala/neon_tutorial­.pdf
  42. Ne10
    http://projectne10.github.io/Ne10/
  43. NEON and Floating-Point architecture
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/BABIGHEB.html
  44. An Introduction to ARM NEON
    http://peterdn.com/post/an-introduction-to-ARM-NEON.aspx
  45. ARM NEON Intrinsics Reference
    http://infocenter.arm.com/hel­p/topic/com.arm.doc.ihi0073a/I­HI0073A_arm_neon_intrinsic­s_ref.pdf
  46. Arm Neon Intrinsics vs hand assembly
    https://stackoverflow.com/qu­estions/9828567/arm-neon-intrinsics-vs-hand-assembly
  47. ARM NEON Optimization. An Example
    http://hilbert-space.de/?p=22
  48. AArch64 NEON instruction format
    https://developer.arm.com/doc­s/den0024/latest/7-aarch64-floating-point-and-neon/73-aarch64-neon-instruction-format
  49. ARM SIMD instructions
    https://developer.arm.com/do­cumentation/dht0002/a/Intro­ducing-NEON/What-is-SIMD-/ARM-SIMD-instructions
  50. Learn the architecture – Migrate Neon to SVE Version 1.0
    https://developer.arm.com/do­cumentation/102131/0100/?lan­g=en
  51. 1.2.2. Comparison between NEON technology and other SIMD solutions
    https://developer.arm.com/do­cumentation/den0018/a/Intro­duction/Comparison-between-ARM-NEON-technology-and-other-implementations/Comparison-between-NEON-technology-and-other-SIMD-solutions?lang=en
  52. NEON Programmer’s Guide
    https://documentation-service.arm.com/static/63299276e68c6809a6b4­1308
  53. 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/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.