Obsah
3. Instrukce přidané v rámci rozšíření F16C
4. Převod vektoru s prvky float na vektor s prvky half a zpět
5. Konverze velkých hodnot, automatický převod na nekonečna
6. Konverze malých hodnot s volbou režimu zaokrouhlení
7. Způsob uložení hodnot typu half float
8. Využití 256bitových vektorů při převodech single/float na half float a zpět
10. Použití intrinsic pro instrukci VFMADD
11. Výpočet multiply-add s desetinnými hodnotami
12. Výpočty s maximálními hodnotami typu single/float
13. Použití intrinsic pro instrukci VFNMADD
14. Použití intrinsic pro instrukci VFMSUB
16. AVX-512 jakožto několik volitelných sad rozšíření instrukcí
17. Možné režimy SIMD s přihlédnutím k možnostem AVX-512
18. Obsah závěrečného článku o SIMD
19. Repositář s demonstračními příklady
1. Rozšíření instrukční sady F16C, FMA a AVX-512 na platformě x86–64, detekce podpory mikroprocesorem
V sedmé a současně i předposlední části miniseriálu o podpoře SIMD operací na platformě x86–64 se seznámíme s rozšířeními instrukční sady, které se jmenují F16C, FMA a AVX-512. Zejména u instrukčních sad FMA (přesněji řečeno FMA4) a AVX-512 se již dostáváme k technologiím, která nemusí být dnes používanými mikroprocesory s architekturou x86–64 podporovány (a to buď proto, že se jedná o příliš nové instrukce, nebo naopak o instrukce již opuštěné či využívané jen konkurencí). Prozatím jsme totiž předpokládali, že MMX, SSE i AVX budou na většině CPU podporovány, ovšem ani u FMA4 ani u AVX-512 to není pravda.
Někdy se tedy nevyhneme runtime testu, zda je nějaké rozšíření podporováno či nikoli. Interně k tomuto účelu slouží instrukce CPUID, ovšem GCC tuto instrukci (a další logiku okolo ní) „obaluje“ v intrinsic nazvanou __builtin_cpu_supports. Této intrinsic je nutné předat řetězcovou konstantu (skutečně konstantu, ne například ukazatel do pole řetězců – takže v programu nepoužívám smyčku) se zkratkou rozšíření; návratovou hodnotou je pak kladná celočíselná hodnota v případě, že je rozšíření podporováno a nula, pokud podporováno není (takže se jedná o klasické céčkovské pravdivostní hodnoty):
#include <stdio.h< int main(void) { printf("Extension SSE is %ssupported\n", __builtin_cpu_supports("sse") ? "" : "un"); printf("Extension SSE2 is %ssupported\n", __builtin_cpu_supports("sse2") ? "" : "un"); printf("Extension SSE3 is %ssupported\n", __builtin_cpu_supports("sse3") ? "" : "un"); printf("Extension SSE4.1 is %ssupported\n", __builtin_cpu_supports("sse4.1") ? "" : "un"); printf("Extension SSE4.2 is %ssupported\n", __builtin_cpu_supports("sse4.2") ? "" : "un"); printf("Extension AVX is %ssupported\n", __builtin_cpu_supports("avx") ? "" : "un"); printf("Extension AVX2 is %ssupported\n", __builtin_cpu_supports("avx2") ? "" : "un"); printf("Extension FMA is %ssupported\n", __builtin_cpu_supports("fma") ? "" : "un"); printf("Extension FMA4 is %ssupported\n", __builtin_cpu_supports("fma4") ? "" : "un"); return 0; }
V mém případě se po překladu a spuštění výše uvedeného programového kódu zobrazí tyto informace:
Extension SSE is supported Extension SSE2 is supported Extension SSE3 is supported Extension SSE4.1 is supported Extension SSE4.2 is supported Extension AVX is supported Extension AVX2 is supported Extension FMA is supported Extension FMA4 is unsupported
$ cat /proc/cpuinfo |head -n 21 processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 142 model name : Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz stepping : 12 microcode : 0xf0 cpu MHz : 900.018 cache size : 8192 KB physical id : 0 siblings : 8 core id : 0 cpu cores : 4 apicid : 0 initial apicid : 0 fpu : yes fpu_exception : yes cpuid level : 22 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp md_clear flush_l1d arch_capabilities bugs : spectre_v1 spectre_v2 spec_store_bypass swapgs taa itlb_multihit srbds mmio_stale_data
2. Rozšíření F16C
Rozšíření F16C obsahuje instrukce určené pro převody mezi vektory, jejichž prvky jsou typu single/float a half float (přesněji half precision floating point). Nejsou zde ovšem přítomny instrukce určené pro výpočty s typy half float, takže se toto rozšíření používá „jen“ pro načtení vektorů s prvky tohoto typu z paměti popř. naopak pro uložení hodnot tohoto typu do paměti. Výpočty budou probíhat s hodnotami single/float nebo double. Díky tomuto přístupu je rozšíření F16C velmi malé, protože obsahuje pouhé dvě instrukce VCVTPH2PS a VCVTPS2PH, které si popíšeme níže. K čemu se však typ half float používá?
Zatímco výše zmíněné formáty single a double jsou určeny pro běžné aritmetické výpočty a při správném použití mohou být využity v mnoha numerických algoritmech, začal být společně s rozšiřováním grafických akcelerátorů (a později s rozvojem neuronových sítí) vyvíjen tlak na standardizaci formátů s menší bitovou hloubkou. Je tomu tak z toho důvodu, že některé operace (již jsme se zmínili o paměti hloubky, ovšem i operace s barvami pixelů atd.) někdy vyžadují vyšší dynamický rozsah, ovšem přesnost nemusí být vysoká a více nám záleží na rychlosti provádění operací.
Dobrým příkladem je dnes již pochopitelně dávno překonaný, ovšem z hlediska vývoje IT velmi důležitý grafický akcelerátor Voodoo I, resp. přesněji řečeno způsob implementace jeho paměti hloubky. Do paměti hloubky (Z-bufferu) je možné ukládat vzdálenosti fragmentů od pozorovatele (kamery) ve dvou formátech, v obou případech je však každý údaj vždy uložen na šestnácti bitech. Při použití prvního způsobu se do Z-bufferu skutečně ukládají vzdálenosti fragmentů, přesněji řečeno celočíselná část vzdálenosti (výpočty vzdálenosti se provádí přesněji, ale výsledek je při ukládání zaokrouhlen).
Tento formát ovšem ve skutečnosti není příliš výhodný, protože po projekci 3D scény ze světových souřadnic do prostoru obrazovky není krok mezi jednotlivými vzdálenostmi konstantní, což vede k vizuálním chybám při vykreslování (rozlišení pouze 216 vzdáleností je v tomto případě nedostatečné). Z tohoto důvodu se preferuje alternativní způsob (nazývaný také w-buffer), při němž se do Z-bufferu ukládají převrácené hodnoty vzdálenosti, a to ve speciálním formátu čísel s pohyblivou řádovou tečkou (čárkou), který má následující strukturu připomínající formát definovaný v IEEE 754:
1.mantissa × 2exponent
V tomto formátu je pro mantisu vyhrazeno dvanáct bitů a pro exponent čtyři bity. Povšimněte si implicitní jedničky před desetinnou tečkou i toho, že žádný bit není vyhrazen pro uložení znaménka – vzdálenosti (a samozřejmě i jejich převrácené hodnoty) jsou vždy kladné. Minimální hodnota, kterou lze tímto způsobem uložit, je rovna jedničce (0×0000 ~ 1.0000000000002×20), maximální hodnota 65528.0 (0×ffff ~ 1.1111111111112×215).
Podobné „krátké“ formáty čísel s plovoucí řádovou tečkou jsou v oblasti grafických akcelerátorů velmi oblíbené. NVidia a firma Microsoft zavedla typ half do jazyka Cg (v roce 2002), ILM podporuje tento formát pro operace vyžadující velkou dynamiku (rozsah) hodnot atd.
Formát half float, jenž je dnes standardizován v IEEE 754–2008, používá pro ukládání hodnot s plovoucí řádovou čárkou pouhých šestnáct bitů, tj. dva byty. Maximální hodnota je rovna 65504, minimální hodnota (větší než nula) přibližně 5,9×10-8. Předností tohoto formátu je malá bitová šířka (umožňuje paralelní přenos po interních sběrnicích GPU) a také větší rychlost zpracování základních operací, protože pro tak malou bitovou šířku mantisy je možné některé operace „zadrátovat“ a nepočítat pomocí ALU. Také některé iterativní výpočty (sin, cos, sqrt) mohou být provedeny rychleji, než v případě plnohodnotných typů float a single.
Celkový počet bitů (bytů): | 16 (2) |
Bitů pro znaménko: | 1 |
Bitů pro exponent: | 5 |
Bitů pro mantisu: | 10 |
BIAS (offset exponentu): | 15 |
Přesnost: | 5–6 číslic |
Maximální hodnota: | 65504 |
Minimální hodnota: | –65504 |
Nejmenší kladná nenulová hodnota: | 5,96×10-8 |
Nejmenší kladná normalizovaná hodnota: | 6,104×10-5 |
Podpora záporné nuly: | ano |
Podpora +∞: | ano |
Podpora -∞: | ano |
Podpora NaN: | ano |
3. Instrukce přidané v rámci rozšíření F16C
Jak jsme si již řekli v předchozí kapitole, byly v rámci rozšíření F16C přidány pouhé dvě instrukce, které existují ve dvou variantách – první varianta je určena pro vektory o šířce 128 bitů a druhá varianta pro vektory o šířce 256 bitů:
Instrukce | Operandy | Stručný popis |
---|---|---|
VCVTPH2PS | xmm,xmm (nebo xmm,mem) | konverze čtyř hodnot typu half na čtyři hodnoty typu single/float |
VCVTPH2PS | ymm,xmm (nebo ymm,mem) | konverze osmi hodnot typu half na osm hodnot typu single/float |
VCVTPS2PH | xmm,xmm,imm8 (nebo mem,xmm,imm8) | konverze čtyř hodnot typu single/float na čtyři hodnoty typu half |
VCVTPS2PH | xmm,ymm,imm8 (nebo mem,ymm,imm8) | konverze osmi hodnot typu single/float na osm hodnot typu half |
Celočíselná konstanta imm8 u obou variant instrukce VCVTPS2PH dovoluje (kromě dalších věcí) specifikovat zaokrouhlovací režim, protože při konverzi single/float → half pochopitelně ztrácíme přesnost:
Hodnota (bity) | Význam |
---|---|
00 | zaokrouhlení na nejbližší hodnotu |
01 | zaokrouhlení směrem dolů |
10 | zaokrouhlení směrem nahoru |
11 | odříznutí nižších bitů |
4. Převod vektoru s prvky float na vektor s prvky half a zpět
Vyzkoušejme si nyní převod 128bitového vektoru obsahujícího čtyři prvky typu float/single na vektor obsahující čtyři prvky typu half float. Tento převod lze realizovat intrinsic nazvanou __builtin_ia32_vcvtps2ph, které se předá vstupní vektor i celočíselná hodnota se zaokrouhlovacím režimem (zde nastaven na 0 – zaokrouhlení na nejbližší hodnotu). Dále provedeme zpětný převod vektoru s prvky typu half float zpět na vektor s prvky typu float/single. Tento zpětný převod je realizován intrinsic se jménem __builtin_ia32_vcvtph2ps. Povšimněte si, že vektor s prvky typu half je typu __v8hi (nebo obdobným vektorem bez znaménkových hodnot):
#include <stdio.h> #include <immintrin.h> int main(void) { __v4sf x = { 0.0, 0.1, 1.0, 3.14 }; __v8hi half; __v4sf y; int i; // konverze float -> half half = __builtin_ia32_vcvtps2ph(x, 0); // konverze half -> float y = __builtin_ia32_vcvtph2ps(half); for (i = 0; i < sizeof(x) / sizeof(float); i++) { printf("%2d %f %04x %f\n", i, x[i], half[i], y[i]); } return 0; }
Překlad obou konverzí do assembleru bude vypadat následovně:
// konverze float -> half half = __builtin_ia32_vcvtps2ph(x, 0); 28: c5 f8 28 45 c0 vmovaps xmm0,XMMWORD PTR [rbp-0x40] 2d: c4 e3 79 1d c0 00 vcvtps2ph xmm0,xmm0,0x0 33: c5 f8 29 45 d0 vmovaps XMMWORD PTR [rbp-0x30],xmm0 // konverze half -> float y = __builtin_ia32_vcvtph2ps(half); 38: c5 f9 6f 45 d0 vmovdqa xmm0,XMMWORD PTR [rbp-0x30] 3d: c4 e2 79 13 c0 vcvtph2ps xmm0,xmm0 42: c5 f8 29 45 e0 vmovaps XMMWORD PTR [rbp-0x20],xmm0
Zajímavější je však výstup z tohoto prográmku. Ukazuje totiž, že se při převodu ze single/float na half float skutečně ztrácí přesnost, což je vidět na zvýrazněných hodnotách:
0 0.000000 0000 0.000000 1 0.100000 2e66 0.099976 2 1.000000 3c00 1.000000 3 3.140000 4248 3.140625
5. Konverze velkých hodnot, automatický převod na nekonečna
Nyní se podívejme na způsob převodu „velkých“ hodnot typu single/float na typ half float a zpět. Slovo „velké“ jsem dal do uvozovek z toho důvodu, že ve skutečnosti je maximální reprezentovatelnou hodnotou pouze 65504. Ostatně se podívejme na následující demonstrační příklad:
#include <stdio.h> #include <immintrin.h> int main(void) { __v4sf x = { 1e3, 1e4, 1e5, 1e6 }; __v8hi half; __v4sf y; int i; // konverze float -> half half = __builtin_ia32_vcvtps2ph(x, 0); // konverze half -> float y = __builtin_ia32_vcvtph2ps(half); for (i = 0; i < sizeof(x) / sizeof(float); i++) { printf("%2d %7.0f %04x %7.0f\n", i, x[i], half[i], y[i]); } return 0; }
Velmi zajímavý je výsledek, který ukazuje, že hodnoty nad 65504 se korektně převedou na nekonečno a pochopitelně zůstávají nekonečnem i při zpětném převodu:
0 1000 63d0 1000 1 10000 70e2 10000 2 100000 7c00 inf 3 1000000 7c00 inf
6. Konverze malých hodnot s volbou režimu zaokrouhlení
V předchozí kapitole jsme si ukázali, jak se velké hodnoty, které se nemohou do rozsahu datového typu half float vejít, převedou na nekonečno. Jak je tomu u malých hodnot, přesněji řečeno u hodnot blízkých nule? V tomto případě se začne projevovat nastavení zaokrouhlovacích režimů, které je ukázáno v následujícím demonstračním příkladu:
#include <stdio.h> #include <immintrin.h> void print_vectors(__v4sf x, __v4sf y, __v8hi half) { int i; for (i = 0; i < sizeof(x) / sizeof(float); i++) { printf("%2d %9.8f %04x %9.8f\n", i, x[i], half[i], y[i]); } putchar('\n'); } int main(void) { __v4sf x = { 5e-4, 5e-5, 5e-6, 5e-7 }; __v8hi half; __v4sf y; // round to nearest even half = __builtin_ia32_vcvtps2ph(x, 0); y = __builtin_ia32_vcvtph2ps(half); print_vectors(x, y, half); // round down half = __builtin_ia32_vcvtps2ph(x, 1); y = __builtin_ia32_vcvtph2ps(half); print_vectors(x, y, half); // round up half = __builtin_ia32_vcvtps2ph(x, 2); y = __builtin_ia32_vcvtph2ps(half); print_vectors(x, y, half); // truncate half = __builtin_ia32_vcvtps2ph(x, 3); y = __builtin_ia32_vcvtph2ps(half); print_vectors(x, y, half); return 0; }
Z výsledků je patrné, jak se zaokrouhlovací režimy projevují. Nejvíce je to vidět ve druhém bloku (hodnoty jsou vždy shodné či nižší) a na bloku třetím (hodnoty jsou vždy shodné či vyšší). Nejmenší souhrnné odchylky jsou podle očekávání v prvním bloku. Mimochodem – druhý a čtvrtý blok mají stejné hodnoty, ovšem pokud změníte převáděné vstupy na záporná čísla, uvidíte podstatný rozdíl:
0 0.00050000 1019 0.00050020 1 0.00005000 0347 0.00005001 2 0.00000500 0054 0.00000501 3 0.00000050 0008 0.00000048 0 0.00050000 1018 0.00049973 1 0.00005000 0346 0.00004995 2 0.00000500 0053 0.00000495 3 0.00000050 0008 0.00000048 0 0.00050000 1019 0.00050020 1 0.00005000 0347 0.00005001 2 0.00000500 0054 0.00000501 3 0.00000050 0009 0.00000054 0 0.00050000 1018 0.00049973 1 0.00005000 0346 0.00004995 2 0.00000500 0053 0.00000495 3 0.00000050 0008 0.00000048
Pro zajímavost se podívejme i na způsob překladu, zejména na význam posledního operandu funkce vcvtps2ph:
// round to nearest even half = __builtin_ia32_vcvtps2ph(x, 0); 97: c5 f8 28 45 d0 vmovaps xmm0,XMMWORD PTR [rbp-0x30] 9c: c4 e3 79 1d c0 00 vcvtps2ph xmm0,xmm0,0x0 a2: c5 f8 29 45 e0 vmovaps XMMWORD PTR [rbp-0x20],xmm0 // round down half = __builtin_ia32_vcvtps2ph(x, 1); ca: c5 f8 28 45 d0 vmovaps xmm0,XMMWORD PTR [rbp-0x30] cf: c4 e3 79 1d c0 01 vcvtps2ph xmm0,xmm0,0x1 d5: c5 f8 29 45 e0 vmovaps XMMWORD PTR [rbp-0x20],xmm0 // round up half = __builtin_ia32_vcvtps2ph(x, 2); fd: c5 f8 28 45 d0 vmovaps xmm0,XMMWORD PTR [rbp-0x30] 102: c4 e3 79 1d c0 02 vcvtps2ph xmm0,xmm0,0x2 108: c5 f8 29 45 e0 vmovaps XMMWORD PTR [rbp-0x20],xmm0 // truncate half = __builtin_ia32_vcvtps2ph(x, 3); 130: c5 f8 28 45 d0 vmovaps xmm0,XMMWORD PTR [rbp-0x30] 135: c4 e3 79 1d c0 03 vcvtps2ph xmm0,xmm0,0x3 13b: c5 f8 29 45 e0 vmovaps XMMWORD PTR [rbp-0x20],xmm0
7. Způsob uložení hodnot typu half float
Hodnoty typu half float používají stejný systém zakódování hodnot, jako je tomu v případě známých datových typů single a double; pouze se snižuje počet bitů rezervovaných pro uložení mantisy i exponentu. Pro mantisu je rezervováno deset bitů a pro exponent pět bitů, přičemž posun exponentu (exponent bias) je 15 (dekadicky). Nejvyšší hodnota exponentu je rezervována pro nekonečna a NaN. Se znalostí těchto vlastností datového typu half float je snadné si vytvořit program, který provede dekódování 16bitové hodnoty zpět na hodnotu s plovoucí řádovou čárkou.
#include <stdio.h> #include <immintrin.h> void decode_half(unsigned short int half) { const int exponent_bias = 15; const int mantissa_base = 1024; const int max_exponent = 31; const int mantissa_bits = 10; const int exponent_bits = 5; unsigned int sign = 0x01 & (half >> (mantissa_bits + exponent_bits)); unsigned int exponent = 0x1f & (half >> mantissa_bits); unsigned int mantissa = 0x03ff & half; if (exponent == max_exponent) { printf("%c infinity\n", sign ? '-' : '+'); } else { printf("%c %8.6f x 2^%-2d\n", sign ? '-' : '+', 1.0 + (float) mantissa / mantissa_base, exponent - exponent_bias); } } void convert_and_decode(__v4sf x) { __v8hi half; int i; // konverze float -> half half = __builtin_ia32_vcvtps2ph(x, 0); for (i = 0; i < sizeof(x) / sizeof(float); i++) { printf("%12.6f %04hx ", x[i], half[i]); decode_half(half[i]); } } int main(void) { __v4sf x = { 0.0, 0.1, 1.0, 2.0 }; convert_and_decode(x); __v4sf y = { 10000, 65400, 65504, 65600 }; convert_and_decode(y); __v4sf z = { 1.0, -1.0, 0.01, -0.01 }; convert_and_decode(z); __v4sf w = {0.001, 0.0001, 0.00001, 0.000001 }; convert_and_decode(w); return 0; }
Z výsledků je patrné například i to, které hodnoty jsou již považovány za nekonečno atd.:
0.000000 0000 + 1.000000 x 2^-15 0.100000 2e66 + 1.599609 x 2^-4 1.000000 3c00 + 1.000000 x 2^0 2.000000 4000 + 1.000000 x 2^1 10000.000000 70e2 + 1.220703 x 2^13 65400.000000 7bfc + 1.996094 x 2^15 65504.000000 7bff + 1.999023 x 2^15 65600.000000 7c00 + infinity 1.000000 3c00 + 1.000000 x 2^0 -1.000000 bc00 - 1.000000 x 2^0 0.010000 211f + 1.280273 x 2^-7 -0.010000 a11f - 1.280273 x 2^-7 0.001000 1419 + 1.024414 x 2^-10 0.000100 068e + 1.638672 x 2^-14 0.000010 00a8 + 1.164062 x 2^-15 0.000001 0011 + 1.016602 x 2^-15
8. Využití 256bitových vektorů při převodech single/float na half float a zpět
Jen pro úplnost se podívejme na dvě zbývající instrinsic nazvané __builtin_ia32_vcvtps2ph256 a __builtin_ia32_vcvtph2ps256. Ty jsou určeny pro konverzi prvků uložených do 256bitových vektorů, což umožňuje provádět paralelní konverze osmi prvků (oběma směry):
#include <stdio.h> #include <immintrin.h> int main(void) { __v8sf x = { 0.0, 0.1, 1.0, 3.14, 1e5, 1e10, 1e15, -1e10 }; __v8hi half; __v8sf y; int i; // konverze float -> half half = __builtin_ia32_vcvtps2ph256(x, 0); // konverze half -> float y = __builtin_ia32_vcvtph2ps256(half); for (i = 0; i < sizeof(x) / sizeof(float); i++) { printf("%2d %8.6g %04hx %f\n", i, x[i], half[i], y[i]); } return 0; }
Hodnoty zobrazené po překladu a spuštění tohoto demonstračního příkladu:
0 0 0000 0.000000 1 0.1 2e66 0.099976 2 1 3c00 1.000000 3 3.14 4248 3.140625 4 100000 7c00 inf 5 1e+10 7c00 inf 6 1e+15 7c00 inf 7 -1e+10 fc00 -inf
Překlad do bajtkódu vypadá následovně (povšimněte si, že jeden z operandů je vždy registr YMMx):
// konverze float -> half half = __builtin_ia32_vcvtps2ph256(x, 0); 3a: c5 fc 28 45 90 vmovaps ymm0,YMMWORD PTR [rbp-0x70] 3f: c4 e3 7d 1d c0 00 vcvtps2ph xmm0,ymm0,0x0 45: c5 f8 29 45 80 vmovaps XMMWORD PTR [rbp-0x80],xmm0 // konverze half -> float y = __builtin_ia32_vcvtph2ps256(half); 4a: c5 f9 6f 45 80 vmovdqa xmm0,XMMWORD PTR [rbp-0x80] 4f: c4 e2 7d 13 c0 vcvtph2ps ymm0,xmm0 54: c5 fc 29 45 b0 vmovaps YMMWORD PTR [rbp-0x50],ymm0
9. Rozšíření FMA
Druhé rozšíření instrukční sady, se kterým se v dnešním článku setkáme, se jmenuje FMA, což je zkratka odvozená z celého názvu Fused Multiply–Add. Z tohoto názvu je možné odvodit, že v tomto rozšíření nalezneme instrukci pro provedení operace a·b + c, ale i další varianty této instrukce, v nichž se navíc mění znaménka jednotlivých operandů popř. pořadí operandů (má význam především ve chvíli, kdy je jeden z operandů uložen v paměti a nikoli v pracovním registru). Účelem tohoto rozšíření je (relativně nepatrně) zvýšit výpočetní rychlost, ale především provést výpočet bez mezizaokrouhlení výsledků. Výpočty probíhají nad typem single či double (skalární varianta) popř. nad vektory s typy single a double.
Ve skutečnosti FMA existuje ve dvou variantách – FMA3 a FMA4. V dnešním článku se zaměříme na FMA3, protože toto rozšíření je v současnosti podporováno jak čipy od AMD, tak i od Intelu (na rozdíl od FMA4, které je prozatím podporováno jen čipy od AMD).
V rozšíření FMA3 lze nalézt jen malé množství nových instrukcí, k jejichž jménům je ještě přidán postfixový kód popsaný níže:
Instrukce | Stručný popis instrukce | Poznámka |
---|---|---|
VFMADD | x = +a · b + c | |
VFNMADD | x = -a · b + c | N – negate |
VFMSUB | x = +a · b – c | |
VFMADDSUB | x = +a · b + c nebo x = +a · b – c | viz další text |
VFMSUBADD | x = +a · b − c nebo x = +a · b + c | viz další text |
U konkrétních instrukcí se, jak jsme si již řekli, uvádí i postfixový kód, který určuje pořadí operandů. Tento postfix uvidíme i v následujících kapitolách:
Postfixový kód | Význam |
---|---|
132 | a = a · c + b |
213 | a = b · a + c |
231 | a = b · c + a |
10. Použití intrinsic pro instrukci VFMADD
Podívejme se nyní na použití intrinsic, která v GCC umožňuje vložení instrukce VFMADD do cílového strojového kódu. Tato instrukce zajišťuje provedení výpočtu x = +a · b + c, tedy vynásobení dvou operandů s přičtením třetího operandu, a to pochopitelně opět pro všechny prvky vektoru:
#include <stdio.h> #include <immintrin.h> void print_results(const char *title, __v4sf * a, __v4sf * b, __v4sf * c, __v4sf * result) { int i; puts(title); for (i = 0; i < sizeof(*a) / sizeof(float); i++) { printf("%2d %1.0f * %1.0f + %1.0f = %1.0f\n", i, (*a)[i], (*b)[i], (*c)[i], (*result)[i]); } putchar('\n'); } int main(void) { __v4sf a = { 1, 2, 3, 4 }; __v4sf b = { 2, 2, 2, 2 }; __v4sf c = { 1, 1, 1, 1 };; __v4sf result; result = __builtin_ia32_vfmaddps(a, b, c); print_results(" # a b c result", &a, &b, &c, &result); }
Po překladu a spuštění tohoto demonstračního příkladu se zobrazí následující (očekávané) výsledky:
# a b c result 0 1 * 2 + 1 = 3 1 2 * 2 + 1 = 5 2 3 * 2 + 1 = 7 3 4 * 2 + 1 = 9
Z přeloženého kódu můžeme vidět, že se použila instrukce VFMADD s postfixem 231 (pořadí operandů) a s určením datového typu ps, což značí vektory hodnot typu single:
result = __builtin_ia32_vfmaddps(a, b, c); f2: c5 f8 28 45 d0 vmovaps xmm0,XMMWORD PTR [rbp-0x30] f7: c5 f8 28 4d c0 vmovaps xmm1,XMMWORD PTR [rbp-0x40] fc: c5 f8 28 55 b0 vmovaps xmm2,XMMWORD PTR [rbp-0x50] 101: c4 e2 69 b8 c1 vfmadd231ps xmm0,xmm2,xmm1 106: c5 f8 29 45 e0 vmovaps XMMWORD PTR [rbp-0x20],xmm0
11. Výpočet multiply-add s desetinnými hodnotami
Pro jistotu je dobré si zkontrolovat, že výpočty typu multiply-add proběhnou korektně (tedy se správnými výsledky) i v těch případech, kdy jsou ve vstupních vektorech uložena desetinná čísla. Předchozí demonstrační příklad tedy upravíme do této podoby (liší se jen hodnoty ve vektorech):
#include <stdio.h> #include <immintrin.h> void print_results(const char *title, __v4sf * a, __v4sf * b, __v4sf * c, __v4sf * result) { int i; puts(title); for (i = 0; i < sizeof(*a) / sizeof(float); i++) { printf("%2d %3.1f * %3.1f + %3.1f = %3.1f\n", i, (*a)[i], (*b)[i], (*c)[i], (*result)[i]); } putchar('\n'); } int main(void) { __v4sf a = { 1, 2, 3, 4 }; __v4sf b = { 0.1, 0.1, 0.1, 0.1 }; __v4sf c = { 1, 1, 1, 1 };; __v4sf result; result = __builtin_ia32_vfmaddps(a, b, c); print_results(" # a b c result", &a, &b, &c, &result); }
Ze zobrazených zpráv je patrné, že i tyto výpočty proběhnou korektně:
# a b c result 0 1.0 * 0.1 + 1.0 = 1.1 1 2.0 * 0.1 + 1.0 = 1.2 2 3.0 * 0.1 + 1.0 = 1.3 3 4.0 * 0.1 + 1.0 = 1.4
Přeložený kód:
result = __builtin_ia32_vfmaddps(a, b, c); f2: c5 f8 28 45 d0 vmovaps xmm0,XMMWORD PTR [rbp-0x30] f7: c5 f8 28 4d c0 vmovaps xmm1,XMMWORD PTR [rbp-0x40] fc: c5 f8 28 55 b0 vmovaps xmm2,XMMWORD PTR [rbp-0x50] 101: c4 e2 69 b8 c1 vfmadd231ps xmm0,xmm2,xmm1 106: c5 f8 29 45 e0 vmovaps XMMWORD PTR [rbp-0x20],xmm0
12. Výpočty s maximálními hodnotami typu single/float
Zajímavé bude zjistit, jak budou výpočty probíhat ve chvíli, kdy se budou nějaké (nenulové) hodnoty násobit konstantou FLT_MAX, což je konstanta reprezentující maximální možnou hodnotu typu single/float, která ještě není považovaná za nekonečno:
#include <stdio.h> #include <immintrin.h> #include <float.h> void print_results(const char *title, __v4sf * a, __v4sf * b, __v4sf * c, __v4sf * result) { int i; puts(title); for (i = 0; i < sizeof(*a) / sizeof(float); i++) { printf("%2d %5.2g * %5.2g + %5.2g = %5.2g\n", i, (*a)[i], (*b)[i], (*c)[i], (*result)[i]); } putchar('\n'); } int main(void) { __v4sf a = { 0.8, 0.9, 1.0, 1.1 }; __v4sf b = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; __v4sf c = { 0, 0, 0, 0 };; __v4sf result; result = __builtin_ia32_vfmaddps(a, b, c); print_results(" # a b c result", &a, &b, &c, &result); }
Výsledky odpovídají očekávání – první dva výsledky jsou menší než FLT_MAX, další je přesně roven FLT_MAX a poslední je již (korektně) považován za nekonečno:
# a b c result 0 0.8 * 3.4e+38 + 0 = 2.7e+38 1 0.9 * 3.4e+38 + 0 = 3.1e+38 2 1 * 3.4e+38 + 0 = 3.4e+38 3 1.1 * 3.4e+38 + 0 = inf
13. Použití intrinsic pro instrukci VFNMADD
V dalším demonstračním příkladu použijeme namísto instrukce VFMADD instrukci nazvanou VFNMADD, která navíc otáčí znaménko operandu a, což znamená, že se jedinou instrukcí provedou tři aritmetické operace, a to navíc nad vektory vstupních operandů:
#include <stdio.h> #include <immintrin.h> void print_results(const char *title, __v4sf * a, __v4sf * b, __v4sf * c, __v4sf * result) { int i; puts(title); for (i = 0; i < sizeof(*a) / sizeof(float); i++) { printf("%2d -%1.0f * %1.0f + %1.0f = %1.0f\n", i, (*a)[i], (*b)[i], (*c)[i], (*result)[i]); } putchar('\n'); } int main(void) { __v4sf a = { 1, 2, 3, 4 }; __v4sf b = { 2, 2, 2, 2 }; __v4sf c = { 1, 1, 1, 1 };; __v4sf result; result = __builtin_ia32_vfnmaddps(a, b, c); print_results(" # a b c result", &a, &b, &c, &result); }
Po překladu a spuštění tohoto demonstračního příkladu si můžeme zkontrolovat, že výpočty jsou opět korektní:
# a b c result 0 -1 * 2 + 1 = -1 1 -2 * 2 + 1 = -3 2 -3 * 2 + 1 = -5 3 -4 * 2 + 1 = -7
Intrinsic __builtin_ia32_vfnmaddps se přeloží do instrukce vfnmadd231ps (opět si povšimněte postfixu u názvu instrukce):
result = __builtin_ia32_vfnmaddps(a, b, c); f2: c5 f8 28 45 d0 vmovaps xmm0,XMMWORD PTR [rbp-0x30] f7: c5 f8 28 4d c0 vmovaps xmm1,XMMWORD PTR [rbp-0x40] fc: c5 f8 28 55 b0 vmovaps xmm2,XMMWORD PTR [rbp-0x50] 101: c4 e2 69 bc c1 vfnmadd231ps xmm0,xmm2,xmm1 106: c5 f8 29 45 e0 vmovaps XMMWORD PTR [rbp-0x20],xmm0
14. Použití intrinsic pro instrukci VFMSUB
Posledním demonstračním příkladem, v němž použijeme instrukce z rozšíření FMA3, je příklad používající instrukci VFMSUB, která namísto výpočtu x = +a · b + c provádí výpočet x = +a · b – c. Ostatně si to můžeme velmi snadno ověřit:
#include <stdio.h> #include <immintrin.h> void print_results(const char *title, __v4sf * a, __v4sf * b, __v4sf * c, __v4sf * result) { int i; puts(title); for (i = 0; i < sizeof(*a) / sizeof(float); i++) { printf("%2d %1.0f * %1.0f - %1.0f = %1.0f\n", i, (*a)[i], (*b)[i], (*c)[i], (*result)[i]); } putchar('\n'); } int main(void) { __v4sf a = { 1, 2, 3, 4 }; __v4sf b = { 2, 2, 2, 2 }; __v4sf c = { 1, 1, 1, 1 };; __v4sf result; result = __builtin_ia32_vfmsubps(a, b, c); print_results(" # a b c result", &a, &b, &c, &result); }
Výsledky:
# a b c result 0 1 * 2 - 1 = 1 1 2 * 2 - 1 = 3 2 3 * 2 - 1 = 5 3 4 * 2 - 1 = 7
Strojový kód získaný překladem vypadá takto:
result = __builtin_ia32_vfmsubps(a, b, c); f2: c5 f8 28 45 d0 vmovaps xmm0,XMMWORD PTR [rbp-0x30] f7: c5 f8 28 4d c0 vmovaps xmm1,XMMWORD PTR [rbp-0x40] fc: c5 f8 28 55 b0 vmovaps xmm2,XMMWORD PTR [rbp-0x50] 101: c4 e2 69 ba c1 vfmsub231ps xmm0,xmm2,xmm1 106: c5 f8 29 45 e0 vmovaps XMMWORD PTR [rbp-0x20],xmm0
15. Rozšíření AVX-512
V závěrečné části dnešního článku se zmíníme o rozšíření instrukční sady nazvané AVX-512. Jak již název tohoto rozšíření částečně napovídá, jedná se o vylepšení (či možná v tomto případě spíše „vylepšení“) rozšíření AVX a AVX2 (Advanced Vector Extensions), s nímž jsme se ve stručnosti seznámili v předchozím článku. Současně nám AVX-512 naznačuje, že se šířka zpracovávaných vektorů opět rozšířila, a to z 256 bitů na celých 512 bitů. To však není vše, protože došlo i k rozšíření počtu vektorových registrů, což může vést k urychlení výpočtů, ale současně se za toto rozšíření „platí“ zdroji na mikroprocesoru (obsazená plocha na čipu + počet použitých tranzistorů):
# | Typ registrů | Počet registrů (x86) | Počet registrů (x86–64) | Bitová šířka registru | Jména registrů |
---|---|---|---|---|---|
1 | Pracovní registry MMX | 8 | 8 | 64 bitů | MM0 .. MM7 |
2 | Pracovní registry SSE | 8 | 16 | 128 bitů | XMM0 .. XMM7 (XMM15) |
3 | Pracovní registry AVX | 8 | 16 | 256 bitů | YMM0 .. YMM7 (YMM15) |
4 | Pracovní registry AVX-512 | 8 | 32 | 512 bitů | ZMM0 .. ZMM31 |
Z výše uvedené tabulky, s níž jsme se již ostatně seznámili minule, je patrný dramatický skok v případě AVX-512, kdy se zdvojnásobil (zečtyřnásobil) počet registrů a současně se i zdvojnásobila jejich bitová šířka. Ve skutečnosti vznikly nové registry AVX se jmény YMMx i registry AVX-512 se jmény ZMMx rozšířením registrů SSE na 256 nebo 512 bitů a přidáním nových registrů. To například znamená, že operace s registrem XMM0 ve skutečnosti může změnit spodních 128 bitů registru YMM0 i ZMM0:
512..256 | 255..128 | 127..0 |
---|---|---|
ZMM0 | YMM0 | XMM0 |
ZMM1 | YMM1 | XMM1 |
ZMM2 | YMM2 | XMM2 |
ZMM3 | YMM3 | XMM3 |
ZMM4 | YMM4 | XMM4 |
ZMM5 | YMM5 | XMM5 |
ZMM6 | YMM6 | XMM6 |
ZMM7 | YMM7 | XMM7 |
ZMM8 | YMM8 | XMM8 |
ZMM9 | YMM9 | XMM9 |
ZMM10 | YMM10 | XMM10 |
ZMM11 | YMM11 | XMM11 |
ZMM12 | YMM12 | XMM12 |
ZMM13 | YMM13 | XMM13 |
ZMM14 | YMM14 | XMM14 |
ZMM15 | YMM15 | XMM15 |
ZMM16 | YMM16 | XMM16 |
ZMM17 | YMM17 | XMM17 |
ZMM18 | YMM18 | XMM18 |
ZMM19 | YMM19 | XMM19 |
ZMM20 | YMM20 | XMM20 |
ZMM21 | YMM21 | XMM21 |
ZMM22 | YMM22 | XMM22 |
ZMM23 | YMM23 | XMM23 |
ZMM24 | YMM24 | XMM24 |
ZMM25 | YMM25 | XMM25 |
ZMM26 | YMM26 | XMM26 |
ZMM27 | YMM27 | XMM27 |
ZMM28 | YMM28 | XMM28 |
ZMM29 | YMM29 | XMM29 |
ZMM30 | YMM30 | XMM30 |
ZMM31 | YMM31 | XMM31 |
16. AVX-512 jakožto několik volitelných sad rozšíření instrukcí
Ve skutečnosti přináší AVX-512 tak rozsáhlou změnu, že je rozděleno do několika podmnožin, přičemž zdaleka na všechny mikroprocesory musí podporovat všechny podmnožiny. To má zajímavý důsledek – počet možných kombinací podporuje/nepodporuje je obrovský. Detailnější popis jednotlivých množin bude uveden příště, ovšem zajímavé bude si vypsat všechny tyto skupiny instrukcí:
Množina instrukcí | Plné jméno | První procesor s implementací |
---|---|---|
F | AVX-512 Foundation | Xeon Phi x200 (Knights Landing), Xeon Gold/Platinum |
CD | AVX-512 Conflict Detection Instructions | Xeon Phi x200 (Knights Landing), Xeon Gold/Platinum |
ER | AVX-512 Exponential and Reciprocal Instructions | Xeon Phi x200 (Knights Landing) |
PF | AVX-512 Prefetch Instructions | Xeon Phi x200 (Knights Landing) |
VL | AVX-512 Vector Length Extensions | Skylake X, Cannon Lake |
DQ | AVX-512 Doubleword and Quadword Instructions | Skylake X, Cannon Lake |
BW | AVX-512 Byte and Word Instructions | Skylake X, Cannon Lake |
IFMA | AVX-512 Integer Fused Multiply Add | Cannon Lake |
VBMI | AVX-512 Vector Byte Manipulation Instructions | Cannon Lake |
4VNNIW | AVX-512 Vector Neural Network Instructions Word variable precision | Knights Mill |
4FMAPS | AVX-512 Fused Multiply Accumulation Packed Single precision | Knights Mill |
VPOPCNTDQ | Vector population count instruction | Knights Mill, Ice Lake |
VNNI | AVX-512 Vector Neural Network Instructions | Ice Lake |
VBMI2 | AVX-512 Vector Byte Manipulation Instructions 2 | Ice Lake |
BITALG | AVX-512 Bit Algorithms | Ice Lake |
VP2INTERSECT | AVX-512 Vector Pair Intersection to a Pair of Mask Registers | Tiger Lake |
Z tohoto důvodu najdeme v GCC C hned několik hlavičkových souborů s hlavičkami intrinsic, které je možné v souvislosti s AVX-512 použít:
avx5124fmapsintrin.h avx5124vnniwintrin.h avx512bitalgintrin.h avx512bwintrin.h avx512cdintrin.h avx512dqintrin.h avx512erintrin.h avx512fintrin.h avx512ifmaintrin.h avx512ifmavlintrin.h avx512pfintrin.h avx512vbmi2intrin.h avx512vbmi2vlintrin.h avx512vbmiintrin.h avx512vbmivlintrin.h avx512vlbwintrin.h avx512vldqintrin.h avx512vlintrin.h avx512vnniintrin.h avx512vnnivlintrin.h avx512vpopcntdqintrin.h avx512vpopcntdqvlintrin.h
Příklad z úvodní kapitoly, který zjistil a vypsal podporovaná rozšíření instrukční sady můžeme snadno rozšířit tak, aby se vypisovaly i informace o jednotlivých podmnožinách AVX-512. Upravená varianta příkladu bude vypadat následovně (opět je nutné používat řetězcové literály, takže příklad nelze jednoduše přepsat do podoby pole+smyčky):
#include <stdio.h> int main(void) { printf("Extension SSE is %ssupported\n", __builtin_cpu_supports("sse") ? "" : "un"); printf("Extension SSE2 is %ssupported\n", __builtin_cpu_supports("sse2") ? "" : "un"); printf("Extension SSE3 is %ssupported\n", __builtin_cpu_supports("sse3") ? "" : "un"); printf("Extension SSE4.1 is %ssupported\n", __builtin_cpu_supports("sse4.1") ? "" : "un"); printf("Extension SSE4.2 is %ssupported\n", __builtin_cpu_supports("sse4.2") ? "" : "un"); printf("Extension AVX is %ssupported\n", __builtin_cpu_supports("avx") ? "" : "un"); printf("Extension AVX2 is %ssupported\n", __builtin_cpu_supports("avx2") ? "" : "un"); printf("Extension FMA is %ssupported\n", __builtin_cpu_supports("fma") ? "" : "un"); printf("Extension FMA4 is %ssupported\n", __builtin_cpu_supports("fma4") ? "" : "un"); putchar('\n'); printf("Extension AVX512F is %ssupported\n", __builtin_cpu_supports("avx512f") ? "" : "un"); printf("Extension AVX512VL is %ssupported\n", __builtin_cpu_supports("avx512vl") ? "" : "un"); printf("Extension AVX512BW is %ssupported\n", __builtin_cpu_supports("avx512bw") ? "" : "un"); printf("Extension AVX512DQ is %ssupported\n", __builtin_cpu_supports("avx512dq") ? "" : "un"); printf("Extension AVX512CD is %ssupported\n", __builtin_cpu_supports("avx512cd") ? "" : "un"); printf("Extension AVX512ER is %ssupported\n", __builtin_cpu_supports("avx512er") ? "" : "un"); printf("Extension AVX512PF is %ssupported\n", __builtin_cpu_supports("avx512pf") ? "" : "un"); printf("Extension AVX512VBMI is %ssupported\n", __builtin_cpu_supports("avx512vbmi") ? "" : "un"); printf("Extension AVX512IFMA is %ssupported\n", __builtin_cpu_supports("avx512ifma") ? "" : "un"); printf("Extension AVX5124VNNIW is %ssupported\n", __builtin_cpu_supports("avx5124vnniw") ? "" : "un"); printf("Extension AVX5124FMAPS is %ssupported\n", __builtin_cpu_supports("avx5124fmaps") ? "" : "un"); printf("Extension AVX512VPOPCNTDQ is %ssupported\n", __builtin_cpu_supports("avx512vpopcntdq") ? "" : "un"); printf("Extension AVX512VBMI2 is %ssupported\n", __builtin_cpu_supports("avx512vbmi2") ? "" : "un"); printf("Extension AVX512BITALG is %ssupported\n", __builtin_cpu_supports("avx512bitalg") ? "" : "un"); return 0; }
V mém konkrétním případě není ani jedna podmnožina AVX-512 podporována, na což se můžeme dívat ze dvou úhlů – jedná se o čip, který „neumí vše“ a „je z pravěku“, ovšem na druhou stranu jsem neplatil za relativně velkou plochu čipu (=tranzistory), které by stejně v naprosté většině aplikací nebyly použity (výjimkou mohou být snad jen kodeky – a i to je možná příliš optimistické očekávání, že budou masivně optimalizovány na všechny možné varianty rozšíření instrukčních sad):
Extension SSE is supported Extension SSE2 is supported Extension SSE3 is supported Extension SSE4.1 is supported Extension SSE4.2 is supported Extension AVX is supported Extension AVX2 is supported Extension FMA is supported Extension FMA4 is unsupported Extension AVX512F is unsupported Extension AVX512VL is unsupported Extension AVX512BW is unsupported Extension AVX512DQ is unsupported Extension AVX512CD is unsupported Extension AVX512ER is unsupported Extension AVX512PF is unsupported Extension AVX512VBMI is unsupported Extension AVX512IFMA is unsupported Extension AVX5124VNNIW is unsupported Extension AVX5124FMAPS is unsupported Extension AVX512VPOPCNTDQ is unsupported Extension AVX512VBMI2 is unsupported Extension AVX512BITALG is unsupported
17. Možné režimy SIMD s přihlédnutím k možnostem AVX-512
Mikroprocesory, které podporují (i když pouze částečně) AVX-512, mohou zpracovávat hodnoty uložené ve vektorech ve více „SIMD režimech“. Pokud budeme na chvíli ignorovat MMX a 3DNow!, jedná se o tyto režimy:
Jméno | Rozšíření | Použité registry | Datové typy |
---|---|---|---|
SSE | SSE-SSE4.2 | XMM0–XMM15 | single/float, SSE2: byte, word, doubleword, quadword, double |
AVX-128 (VEX) | AVX, AVX2 | XMM0–XMM15 | byte, word, doubleword, quadword, single/float, double |
AVX-256 (VEX) | AVX, AVX2 | YMM0–YMM15 | single/float, double. From AVX2: byte, word, doubleword, quadword |
AVX-128 (EVEX) | AVX-512VL | XMM0–XMM31 | doubleword, quadword, single/float, double. AVX512BW: byte, word. AVX512-FP16: half float |
AVX-256 (EVEX) | AVX-512VL | YMM0–YMM31 | doubleword, quadword, single/float, double. AVX512BW: byte, word. AVX512-FP16: half float |
AVX-512 (EVEX) | AVX-512F | ZMM0–ZMM31 | doubleword, quadword, single/float, double. AVX512BW: byte, word. AVX512-FP16: half float |
18. Obsah závěrečného článku o SIMD
V závěrečném článku o SIMD operacích podporovaných (i když nepřímo) překladačem GCC C se zaměříme přímo na ty instrukce, které lze nalézt v jednotlivých rozšířeních instrukčních sad AVX-512.
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ář:
# | Příklad | Stručný popis | Adresa |
---|---|---|---|
1 | simd01.c | vektor celých čísel typu short int | https://github.com/tisnik/presentations/blob/master/SIMD/simd01.c |
2 | simd02.c | ukázka použití vektorů s celočíselnými typy bez znaménka | https://github.com/tisnik/presentations/blob/master/SIMD/simd02.c |
3 | simd03.c | ukázka použití vektorů s celočíselnými typy se znaménkem | https://github.com/tisnik/presentations/blob/master/SIMD/simd03.c |
4 | simd04.c | paralelní součet celočíselných prvků vektorů | https://github.com/tisnik/presentations/blob/master/SIMD/simd04.c |
5 | simd04B.c | úprava pro další datové typy | https://github.com/tisnik/presentations/blob/master/SIMD/simd04B.c |
6 | simd05.c | přístup k jednotlivým prvkům vektorů | https://github.com/tisnik/presentations/blob/master/SIMD/simd05.c |
7 | simd05B.c | korektnější výpočet počtu prvků vektoru | https://github.com/tisnik/presentations/blob/master/SIMD/simd05B.c |
8 | simd05C.c | definice typu vektoru | https://github.com/tisnik/presentations/blob/master/SIMD/simd05C.c |
9 | simd06.c | vektor čísel s plovoucí řádovou čárkou | https://github.com/tisnik/presentations/blob/master/SIMD/simd06.c |
10 | simd07.c | paralelní součet prvků vektorů (typ float) | https://github.com/tisnik/presentations/blob/master/SIMD/simd07.c |
11 | simd08.c | paralelní součet prvků vektorů (typ double) | https://github.com/tisnik/presentations/blob/master/SIMD/simd08.c |
12 | simd09.c | překročení délky vektoru | https://github.com/tisnik/presentations/blob/master/SIMD/simd09.c |
13 | simd10.c | přístup k jednotlivým prvkům vektorů | https://github.com/tisnik/presentations/blob/master/SIMD/simd10.c |
14 | simd11.c | překročení délky vektoru | https://github.com/tisnik/presentations/blob/master/SIMD/simd11.c |
15 | simd12.c | dlouhý vektor s 256 bajty | https://github.com/tisnik/presentations/blob/master/SIMD/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/presentations/blob/master/SIMD/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/presentations/blob/master/SIMD/simd14.c |
18 | simd15.c | operace součtu pro vektory s prvky rozličné bitové šířky s plovoucí řádovou čárkou | https://github.com/tisnik/presentations/blob/master/SIMD/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/presentations/blob/master/SIMD/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/presentations/blob/master/SIMD/simd17.c |
21 | simd18.c | všechny podporované binární operace nad vektory s prvky typu float | https://github.com/tisnik/presentations/blob/master/SIMD/simd18.c |
23 | intrinsic_mmx1.c | intrinsic pro technologii MMX: instrukce paddb | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_mmx1.c |
24 | intrinsic_mmx2.c | intrinsic pro technologii MMX: instrukce paddw | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_mmx2.c |
25 | intrinsic_mmx3.c | intrinsic pro technologii MMX: instrukce paddb (přetečení) | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_mmx3.c |
26 | intrinsic_mmx4.c | intrinsic pro technologii MMX: instrukce paddsb (saturace) | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_mmx4.c |
27 | intrinsic_mmx5.c | intrinsic pro technologii MMX: instrukce pupckhbw (kombinace dvou vektorů) | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_mmx5.c |
28 | intrinsic_sse1.c | součet dvou vektorů s šestnácti prvky typu char instrukcí paddb128 | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse1.c |
29 | intrinsic_sse2.c | součet dvou vektorů s osmi prvky typu short instrukcí paddw128 | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse2.c |
30 | intrinsic_sse3.c | součet dvou vektorů se čtyřmi prvky typu int instrukcí paddd128 | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse3.c |
31 | intrinsic_sse4.c | součet dvou vektorů se dvěma prvky typu long instrukcí paddq128 | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse4.c |
32 | intrinsic_sse5.c | součet dvou vektorů se čtyřmi prvky typu float instrukcí addps | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse5.c |
33 | intrinsic_sse6.c | součet dvou vektorů se dvěma prvky typu double instrukcí addpd | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse6.c |
34 | intrinsic_sse7.c | porovnání celočíselných prvků instrukcemi pcmpeqb128 a pcmpgtb128 | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse7.c |
35 | intrinsic_sse8.c | všech šest relačních operací pro vektory s prvky typu float | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse8.c |
36 | intrinsic_sse9.c | unární operace pro výpočet převrácené hodnoty, druhé odmocniny a převrácené hodnoty druhé odmocniny | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse9.c |
37 | intrinsic_sse_A.c | instrukce shufps a její intrinsic | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse_A.c |
38 | intrinsic_sse_B.c | instrukce unpckhps a unpcklps a jejich intrinsics | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_sse_B.c |
39 | simd19.c | operace součtu vektorů o délce 256 bitů s celočíselnými prvky bez znaménka | https://github.com/tisnik/presentations/blob/master/SIMD/simd19.c |
40 | simd20.c | operace součtu vektorů o délce 256 bitů s celočíselnými prvky se znaménkem | https://github.com/tisnik/presentations/blob/master/SIMD/simd20.c |
41 | simd21.c | operace součtu vektorů o délce 256 bitů s prvky typu float a double | https://github.com/tisnik/presentations/blob/master/SIMD/simd21.c |
42 | test_extensions.c | test, které instrukční sady mikroprocesor podporuje | https://github.com/tisnik/presentations/blob/master/SIMD/test_extensions.c |
43 | test_avx512_extensions.c | test, které instrukční sady mikroprocesor podporuje, rozšíření o AVX-512 | https://github.com/tisnik/presentations/blob/master/SIMD/test_avx512_extensions |
44 | intrinsic_f16c1.c | převod 128bitového vektoru s prvky typu float na half a zpět | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_f16c1.c |
45 | intrinsic_f16c2.c | převod 128bitového vektoru s prvky typu float na half a zpět, zpracování velkých hodnot | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_f16c2.c |
46 | intrinsic_f16c3.c | převod 128bitového vektoru s prvky typu float na half s volbou režimu zaokrouhlení | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_f16c3.c |
47 | intrinsic_f16c4.c | převod 256bitového vektoru s prvky typu float na half a zpět | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_f16c4.c |
48 | intrinsic_fma3_1.c | využití instrukce pro provedení vektorové operace x = a*b+c | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_fma3_1.c |
49 | intrinsic_fma3_2.c | dtto, ale pro odlišné hodnoty | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_fma3_2.c |
50 | intrinsic_fma3_3.c | dtto, ale vynásobení maximálními možnými FP hodnotami | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_fma3_3.c |
51 | intrinsic_fma3_4.c | využití instrukce pro provedení vektorové operace x = -a*b+c | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_fma3_4.c |
52 | intrinsic_fma3_5.c | využití instrukce pro provedení vektorové operace x = a*b-c | https://github.com/tisnik/presentations/blob/master/SIMD/intrinsic_fma3_5.c |
53 | decode_half_float.c | dekódování hodnot s plovoucí řádovou čárkou uložených ve formátu half float | https://github.com/tisnik/presentations/blob/master/SIMD/decode_half_float.c |
54 | Makefile | Makefile pro překlad všech výše uvedených demonstračních příkladů | https://github.com/tisnik/presentations/blob/master/SIMD/Makefile |
Soubory vzniklé překladem z jazyka C do assembleru procesorů x86–64:
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/ - Other Built-in Functions Provided by GCC
https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html - GCC: 6.60 Built-in Functions Specific to Particular Target Machines
https://gcc.gnu.org/onlinedocs/gcc/Target-Builtins.html#Target-Builtins - Advanced Vector Extensions
https://en.wikipedia.org/wiki/Advanced_Vector_Extensions - AVX-512
https://en.wikipedia.org/wiki/AVX-512 - AVX-512
https://iq.opengenus.org/avx512/ - Downclocking pro AVX-512
https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Downclocking - BLENDPS — Blend Packed Single Precision Floating-Point Values
https://www.felixcloutier.com/x86/blendps - BLENDPD — Blend Packed Double Precision Floating-Point Values
https://www.felixcloutier.com/x86/blendpd - Why Intel is betting on BFLOAT16 to be a game changer for deep learning training? Hint: Range trumps Precision
https://hub.packtpub.com/why-intel-is-betting-on-bfloat16-to-be-a-game-changer-for-deep-learning-training-hint-range-trumps-precision/ - half-rs (pro Rust)
https://github.com/starkat99/half-rs - float16 (pro Go)
https://github.com/x448/float16 - bfloat16 – Hardware Numerics Definition
https://software.intel.com/en-us/download/bfloat16-hardware-numerics-definition - Intel Prepares To Graft Google’s Bfloat16 Onto Processors
https://www.nextplatform.com/2019/07/15/intel-prepares-to-graft-googles-bfloat16-onto-processors/ - A Study of BFLOAT16 for Deep Learning Training
https://arxiv.org/pdf/1905.12322.pdf - BFloat16s.jl
https://github.com/JuliaComputing/BFloat16s.jl - Half Precision Arithmetic: fp16 Versus bfloat16
https://nhigham.com/2018/12/03/half-precision-arithmetic-fp16-versus-bfloat16/ - bfloat16 floating-point format (Wikipedia)
https://en.wikipedia.org/wiki/Bfloat16_floating-point_format - Unum (number format)
https://en.wikipedia.org/wiki/Unum_(number_format)#Posit - Performance Benefits of Half Precision Floats
https://software.intel.com/en-us/articles/performance-benefits-of-half-precision-floats - Norma IEEE 754 a příbuzní: formáty plovoucí řádové tečky
https://www.root.cz/clanky/norma-ieee-754-a-pribuzni-formaty-plovouci-radove-tecky/ - IEEE-754 Floating-Point Conversion
http://babbage.cs.qc.cuny.edu/IEEE-754.old/32bit.html - Small Float Formats
https://www.khronos.org/opengl/wiki/Small_Float_Formats - Binary-coded decimal
https://en.wikipedia.org/wiki/Binary-coded_decimal - Floating-Point Formats
http://www.quadibloc.com/comp/cp0201.htm - Data types (SciPy)
https://docs.scipy.org/doc/numpy-1.13.0/user/basics.types.html - New 16-bit floating point type – NumPy 1.6.0 Release Notes
https://github.com/numpy/numpy/blob/7cfec2403486456b52b525eccf7541e1562d9ab3/doc/release/1.6.0-notes.rst#new-16-bit-floating-point-type - RFC pro Rust
https://github.com/joshtriplett/rfcs/blob/f16b/text/0000-f16b.md - IEEE-754 Floating Point Converter
https://www.h-schmidt.net/FloatConverter/IEEE754.html - Mediump float calculator
https://oletus.github.io/float16-simulator.js/ - IEEE 754 Calculator
http://weitz.de/ieee/ - BFloat16 (Swift for TensorFlow)
https://www.tensorflow.org/swift/api_docs/Structs/BFloat16 - Using bfloat16 with TensorFlow models
https://cloud.google.com/tpu/docs/bfloat16 - What is tf.bfloat16 “truncated 16-bit floating point”?
https://stackoverflow.com/questions/44873802/what-is-tf-bfloat16-truncated-16-bit-floating-point - BFloat16 processing for Neural Networks on Armv8-A
https://community.arm.com/developer/ip-products/processors/b/ml-ip-blog/posts/bfloat16-processing-for-neural-networks-on-armv8_2d00_a - Mixed precision training
https://arxiv.org/pdf/1710.03740.pdf - [R] Mixed Precision Training
https://www.reddit.com/r/MachineLearning/comments/75phd2/r_mixed_precision_training/ - Floating Point Numbers
https://floating-point-gui.de/formats/fp/ - Float exposed
https://float.exposed/0×40490000 - Float Toy
http://evanw.github.io/float-toy/ - IEEE-754 visualization
https://bartaz.github.io/ieee754-visualization/ - Advantages Of BFloat16 For AI Inference
https://semiengineering.com/advantages-of-bfloat16-for-ai-inference/ - ARMv8-A bude podporovat nový formát čísel BFloat16
https://www.root.cz/zpravicky/armv8-a-bude-podporovat-novy-format-cisle-bfloat16/ - Intel oznámil nový formát BFloat16 pro budoucí procesory
https://www.root.cz/zpravicky/intel-oznamil-novy-format-bfloat16-pro-budouci-procesory/ - Nový formát čísel Intelu BFloat16 bude v GCC 10 a Clang 9
https://www.root.cz/zpravicky/novy-format-cisel-intelu-bfloat16-bude-v-gcc-10-a-clang-9/ - Mixed precision
https://www.tensorflow.org/guide/keras/mixed_precision - Training Performance: A user’s guide to converge faster (TensorFlow Dev Summit 2018)
https://www.youtube.com/watch?v=SxOsJPaxHME - Programování GPU na Raspberry Pi: použití Quad Processor Unit(s)
https://www.root.cz/clanky/programovani-gpu-na-raspberry-pi-pouziti-quad-processor-unit-s/ - “Half Precision” 16-bit Floating Point Arithmetic
https://blogs.mathworks.com/cleve/2017/05/08/half-precision-16-bit-floating-point-arithmetic/ - Half Precision Arithmetic in Numerical Linear Algebra
https://nla-group.org/2018/10/03/half-precision-arithmetic-in-numerical-linear-algebra/ - Enable BF16 support
https://gcc.gnu.org/ml/gcc-patches/2019–04/msg00477.html - Survey of Floating-Point Formats
https://mrob.com/pub/math/floatformats.html - VCVTPS2PH — Convert Single-Precision FP value to 16-bit FP value
https://www.felixcloutier.com/x86/vcvtps2ph - VCVTPH2PS — Convert 16-bit FP values to Single-Precision FP values
https://www.felixcloutier.com/x86/vcvtph2ps