Hlavni problem pouziti techto instrukci vidim v tom, ze kazdy prekladac to ma jinak, navic je potreba resit fallbacky pro starsi architektury. Instrukce jsou dost podobne, casto jde pouze o to, ze novejsi zvladaji operace na vice prvcich, takze tech fallbacku muze byt docela hodne.
Samozrejme pro konkretni pouziti na konkretnim stroji to neni takovy problem, ale pro program, ktery obecne bezi kdekoliv je to fakt otrava. Pokud chci mit fakt jistotu, tak je nejlepsi moznost konkretni funkci napsat v NASM (nebo alternative), ale bohuzel stejne musim mit takovych funkci mit nekolik pro ruzne varianty.
V posledni dobe jsem to spise vzdal a pouzivam OpenCL a co jsem zkousel a mam z toho docela dobry pocit je OMP simd. Jeste jsem se dival na SyCL, ale v dobe, kdy jsem se na to dival to nemelo a mozna porad nema nejakou rozumnou implementaci.
Tak treba s takovym SSE se uz da pocitat vsude. Problematicky bude dneska uz jen AVX-512 rekl bych. Jako uplne nejlepsi reseni je binarka, co si nataha .so-cko podle moznosti te architektury, takze by tam bylo nejakych 3-5 konfiguraci max. To neni tak zly.
OpenCL ten problem neresi, protoze vyzaduje SSE3 ne?
V zásadě budou asi stačit ty 3 konfigurace - SSEx (2 nebo cokoliv je dneska standard), AVX-2 a AVX-512. Pro ARM Neon and SVE.
K předchozímu - není extra dobré to psát přímo v ASM, bo: (a) nemusí to být dobře integrované s kompilátorem, v rámci vyšších funkcí, kde se aplikuje inline, (b) v rámci inline je kompilátor lépe schopen distribuovat registry podle svých potřeb nebo udržet danou proměnnou v registru apod. Řešení je buď intrinsic, které se téměř přímo přeloží (občas je kompilátor ještě chytřejší - občas až moc, že je z toho bug), nebo to nechat přímo na kompilátor - umí vektorizaci už většinou dobře, pokud se mu dají volné ruce (třeba použití FMA může být problém, bo to vede ke správnějším výsledkům a musí se tedy explicitně povolit). To druhé má výhodu, že se kód vezme a jenom se třikrát přeloží pro různé architektury, takže je to bez práce.
Jestli vylepšit samotný kompilátor, aby uměl složitější funkce jako saturace - možná je to spíš lepší nechat na knihovnu, než cpát všechno do kompilátoru... Třeba GLM je pěkná na operace pro 3D grafiku.
To je pravda, nejrozsirenejsi architekturou na PC je x64 a tam uz je aspon zakladni SSE vzdycky.
S tim OpenCL mas taky pravdu, minimalne tam musis mit fallback pro pripad, ze dany host OpenCL nepodporuje (tady se samozrejme bavime o vektorizaci na CPU, normalne se da pocitat s tim, ze host bude mit i GPU).
Problem je ten, ze 3-5 konfiguraci je i tak dost, kdo to ma udrzovat? Typicky bych asi dal jako priklad TurboJPEG knihovnu, na vetsinu obrazku funguje naprosto spolehlive, ale dej mu na vstup nejakou nepouzivanou variantu a v nejlepsim pripade zrovna tohle nemaji akcelerovane, tak to trva, v horsim to vyplivne spatny obrazek a v nejhorsim to spadne. Dost takovych bugu vyresili, ale udrzovat to musi byt velky opruz. A to se jedna o knihovnu, kterou treba prohlizece dokazi a chteji zasponzorovat.
Ono je rozumné neimplementovat kód přímo v Intel intristic ale na GCC s využitím
typedef int v4si __attribute__ ((vector_size (16))); v4si a, b, c; c = a + b;
https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Vector-Extensions.html#Vector-Extensions
Bude to chodit jak pro x86, ARM, RISC-V a GCC rovnou garantuje, že pokud je navolená při kompilaci architektura, která danou šířku neumí, tak operace realizuje sekvencemi užších až případně skalárních operací.
Obecně ale celý přístup se SIMD operacemi, u kterých je délka natvrdo zakódovaná do instrukce dobře použitelný jen pro vektory pevné délky, jako je třeba homogenní souřadnice 4x float atd... Pro urychlování smyček nad vektory s runtime definovanou délkou vychází kód ošklivě, vetšinou nějaký skalární start pro zarovnání, pak SIMD tělo a skalární dopočítání zbytku. To chodí použitelně pro dlouhé vektory, pro délku okolo 10, 20 prvků je to katastrofa a kompilátor často nemá jak poznat, jestli bude vektor 10 prvků nebo třeba ze vstupu po síti přijde 1000 prvků pole. Takže rozumná, úspěšná autovektorizce je na zastaralé architektury jako je x86 nemožná.
Proto se RISC-V inspirovalo spíše Seymoura Craye ze sedmesátých let a definuje vektorové instrukce s dynamicky nastavovanou celkovou délkou operace. HW pak rozdělí výpočet na skupiny podle aktuálně implementované šířky vektorové ALU s tím kód nepotřebuje rozhodování, jestli se vektorizace vyplatí, není žádný loop startup and finish specific kód a i program zkompilovaný pro starší nebo jen levnější implementaci s užší ALU při spuštění na širší automaticky využije plnou délku.
Viz https://github.com/riscv/riscv-v-spec/releases/tag/v1.0
Případně článek SIMD Instructions Considered Harmful
https://www.sigarch.org/simd-instructions-considered-harmful/
V zásadě to tedy není, že kompilátory nestíhají, ale že Intel a další opět předvedli, že táhnou rozvoj od sedmdesátých let špatným směrem, protože již dříve to bylo vyřešené správně. RISC-V a teď i AArch64 ARM přes SVE špatný směr napravují.
kód nepotřebuje rozhodování, jestli se vektorizace vyplatí, není žádný loop startup and finish specific kód
To není tak úplně pravda - i Aarch64 SVE2 má omezení, že operace probíhají na násobku 128-bit vektorů. Takže pro skutečně jakoukoliv velikost tam bude muset být ošetření modulo 4 (pro float) či modulo 2 (pro double). Navíc jsou tam další omezení - třeba multiply lane lze udělat s prvkem ze čtveřice float vektorů, ale už ne z jakékoli n-tice či s double. Pravda je, že na 3D grafiku to přesně sedí, takže tam to postačuje. RISC-V tak moc neznám.
Jestli měl Intel myslet víc dopředu - Cray přece jen mířil na jinou cílovou skupinu (i když o 20 let dřív), v té době jednoduché SSE asi nebyl špatný krok. Ostatně, i pozdější Aarch64 měl jenom jednoduchý Neon, SVE přišlo až později. Na druhé straně je třeba říct, že ty výsledky jsou o dost lepší - Graviton 3 (Aarch64 s SVE) mi počítal násobení matice vektorem (4*4 * 4) za půl taktu, zatímco x86-64 se mi povedlo dostat na 1.5 taktů. Možná se ale x86-64 s posledními Ryzen posunulo...
RISC-V SIMD je nejhorší SIMD co vůbec existuje, pravděpodobně navržený lidma, co SIMD nepoužívají.
Ono "spojovat" registry a definovat operace nad více registry je totálně nepraktická myšlenka, protože když mám 32 registrů, a udělám operace nad 4 registry (takže třeba ADD bude adresovat 4 + 4 + 4 registry), tak sice na oko zvětším šířku operace, ale efektivně se mi sníží počet registrů na 8, protože budu používat skupiny po 4 registrech. Ani radši nemluvím o tom, že SIMD kód potřebuje konstanty, a většinou něco netriviálního hodně konstant, takže toto celé je úplně k ničemu.
Radši ani nemluvím o tom, že RISC-V V má jen jeden maskovací registr, který je ještě sdílený se SIMD registrem - opět něco co nenajdeme v AVX-512 nebo SVE.
RISC-V V je prostě instrukční sada ušitá horkou jehlou, a úplně mám pocit, že snad Intel nebo někdo to úmyslně sabotoval :)
Zdá se, že jsou programátoři, kteří si myslí, že ten návrh je lepší než stovky SIMD s pevnou délkou a mě to také tak připadá i po tom, co jsem si něco na x86 zkusil napsat, kde zrovna délka prvku nebyla mocninou dvou a ani data nebyla zarovnaná. Ty začátky a konce daly dost zabrat a možnost nastavit, že se má pracovat jen s částí délky registru by hodně pomohla...
https://itnext.io/advantages-of-risc-v-vector-processing-over-x86-simd-c1b72f3a3e82