Obsah
1. Užitečné rozšíření GCC – podpora SIMD (vektorových) instrukcí: nedostatky
2. Vyplatí se používat dlouhé vektory?
3. Předání vektorů do funkce hodnotou vs. referencí
4. Způsob překladu vektorových operací do assembleru
5. Tytéž operace, ovšem prováděné s prvky se znaménkem
6. Způsob překladu vektorových operací do assembleru
7. Vektorové operace nad vektory s prvky typu float a double
9. Vektor není libovolně velké pole – omezení GCC
10. Způsob překladu operace s dlouhým vektorem do assembleru
11. Ne všechny operace mohou být v SIMD podporovány
12. Výsledek překladu předchozího programu do kódu se SIMD operacemi
13. Základní aritmetické operace s prvky vektorů typu float
14. Překlad bez použití instrukcí SIMD
15. Překlad do strojového kódu s použitím instrukcí SIMD
16. Zásadní omezení rozšíření (sic) GCC
17. SIMD a vektorové operace na architekturách AArch64 a RISC-V
18. Příloha – soubor Makefile použitý v dnešním článku
19. Repositář s demonstračními příklady
1. Užitečné rozšíření GCC – podpora SIMD (vektorových) instrukcí: nedostatky
Na úvodní článek o problematice využití SIMD operací na moderních mikroprocesorech v jazyku C dnes navážeme. Prozatím jsme si řekli, jaké SIMD operace můžeme nalézt v rozšíření instrukčních sad pojmenovaných MMX, 3DNow!, SSE a SSE2, což jsou (kromě zastaralé 3DNow!) rozšíření, která nalezneme v prakticky všech dnes používaných čipech s architekturou x86 (x86–64) a bylo by dobré je začít ve vyšší míře nějakým způsobem používat. Taktéž víme, že rozšíření překladače GCC C dokáže do určité míry tyto rozšířené instrukční sady skutečně využívat, protože programátorům nabízí základní aritmetické a logické operace prováděné s vektory.
Ovšem toto řešení je ve skutečnosti pouze částečné, neboť má mnohé nedostatky, o nichž se zmíníme v dnešním článku. Taktéž si ukážeme, že vektory zavedené v rámci tohoto rozšíření není vhodné slepě používat namísto polí (o předem zadané délce), neboť to nemusí vést k optimálnímu kódu.
2. Vyplatí se používat dlouhé vektory?
Podívejme se nyní na následující demonstrační příklad, v němž je použit relativně dlouhý vektor s celkem 256/4 = 64 prvky typu float. Vektor zde tedy používáme ve funkci pole:
typedef float v256f __attribute__((vector_size(256))); int main(void) { v256f x = { 1.0 }; v256f y = { 1.0 }; v256f z = x + y; return 0; }
Tento demonstrační příklad je sice plně funkční, ovšem i tak je potenciálně problematický. Na jeden problém narazíme vcelku rychle – tímto způsobem lze totiž tvořit a používat pouze vektory s délkou, jenž je celočíselnou mocninou dvojky. O tomto omezení se můžeme snadno přesvědčit ve chvíli, kdy se pokusíme délku vektoru zvětšit o jeden prvek typu float, tedy celkem o čtyři bajty (z 256 bajtů na 260 bajtů):
typedef float v260f __attribute__((vector_size(260))); int main(void) { v260f x = { 1.0 }; v260f y = { 1.0 }; v260f z = x + y; return 0; }
Při pokusu o překlad tohoto zdrojového kódu nahlásí překladač chybu:
simd12B.c:1:1: error: number of components of the vector not a power of two 1 | typedef float v260f __attribute__((vector_size(260))); | ^~~~~~~
Ovšem problémů je ve skutečnosti více – vektory se implicitně předávají hodnotou a nikoli referencí (jako pole), vektorové operace se (na x86–64) vždy provádí sekvencí instrukcí bez smyček atd. Tyto potenciální problémy, které vlastně rostou s délkou vektorů, si popíšeme v navazujících kapitolách.
3. Předání vektorů do funkce hodnotou vs. referencí
Pole se v programovacím jazyku C předávají referencí, o čemž se můžeme velmi snadno přesvědčit překladem a spuštěním následujícího demonstračního příkladu:
#include <stdio.h> typedef int array[4]; void foo(array a) { a[1] = 42; } int main(void) { array x = { 1, 2, 3, 4 }; printf("%d %d %d %d\n", x[0], x[1], x[2], x[3]); foo(x); printf("%d %d %d %d\n", x[0], x[1], x[2], x[3]); }
Pokud by se pole předávalo hodnotou, pracovala by funkce foo s kopií pole a zobrazené zprávy by měly vypadat takto:
1 2 3 4 1 2 3 4
Ve skutečnosti se pole předává referencí a proto je možné ve funkci foo modifikovat jeho prvky:
1 2 3 4 1 42 3 4
U vektorů je tomu však jinak, o čemž se opět můžeme velmi snadno přesvědčit:
#include <stdio.h> typedef int vector __attribute__((vector_size(16))); void foo(vector v) { v[1] = 42; } int main(void) { vector x = { 1, 2, 3, 4 }; printf("%d %d %d %d\n", x[0], x[1], x[2], x[3]); foo(x); printf("%d %d %d %d\n", x[0], x[1], x[2], x[3]); }
Zde při překladu a spuštění dostaneme:
1 2 3 4 1 2 3 4
Tento výsledek ukazuje, že se vektory předávají hodnotou, což může být náročná operace, zejména u delších vektorů.
4. Způsob překladu vektorových operací do assembleru
Vyzkoušejme si nyní, jak se přeloží funkce provádějící součet vektorů do assembleru. Vše si otestujeme na funkcích, které sčítají vektory s prvky typu unsigned char, unsigned short, unsigned int a unsigned long:
#include <stdio.h> typedef unsigned char v16ub __attribute__((vector_size(16))); void add16ub(v16ub x, v16ub y, v16ub * z) { *z = x + y; } typedef unsigned short v16us __attribute__((vector_size(16))); void add16us(v16us x, v16us y, v16us * z) { *z = x + y; } typedef unsigned int v16ui __attribute__((vector_size(16))); void add16ui(v16ui x, v16ui y, v16ui * z) { *z = x + y; } typedef unsigned long int v16ul __attribute__((vector_size(16))); void add16ul(v16ul x, v16ul y, v16ul * z) { *z = x + y; } int main(void) { { v16ub x = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; v16ub y = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; v16ub z; add16ub(x, y, z); int i; puts("vector of unsigned chars"); for (i = 0; i < sizeof(v16ub) / sizeof(unsigned char); i++) { printf("%d %u\n", i, z[i]); } } putchar('\n'); { v16us x = { 0, 1, 2, 3, 4, 5, 6, 7 }; v16us y = { 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff }; v16us z; add16us(x, y, z); int i; puts("vector of unsigned short ints"); for (i = 0; i < sizeof(v16us) / sizeof(unsigned short); i++) { printf("%d %u\n", i, z[i]); } } putchar('\n'); { v16ui x = { 0, 1, 2, 3 }; v16ui y = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; v16ui z; add16ui(x, y, z); int i; puts("vector of unsigned ints"); for (i = 0; i < sizeof(v16ui) / sizeof(unsigned int); i++) { printf("%d %u\n", i, z[i]); } } putchar('\n'); { v16ul x = { 0, 1 }; v16ul y = { 0xffffffffffffffff, 0xffffffffffffffff }; v16ul z; add16ul(x, y, z); int i; puts("vector of unsigned longs"); for (i = 0; i < sizeof(v16ul) / sizeof(unsigned long); i++) { printf("%d %lu\n", i, z[i]); } } return 0; }
S výsledky:
vector of unsigned chars 0 255 1 0 2 1 3 2 4 3 5 4 6 5 7 6 8 7 9 8 10 9 11 10 12 11 13 12 14 13 15 14 vector of unsigned short ints 0 65535 1 0 2 1 3 2 4 3 5 4 6 5 7 6 vector of unsigned ints 0 4294967295 1 0 2 1 3 2 vector of unsigned longs 0 18446744073709551615 1 0
Překlad výše uvedeného kódu do assembleru vypadá takto.
Nejdříve se při zákazu SIMD operací provede maskování bajtů, 16bitových slov nebo 32bitových slov a paralelní výpočty v rámci 64bitových registrů. Tím se simuluje součet po bajtech, 16bitových slovech a 32bitových slovech:
c: 48 8b 45 10 mov rax,QWORD PTR [rbp+0x10] 10: 48 8b 55 20 mov rdx,QWORD PTR [rbp+0x20] 14: 48 89 c6 mov rsi,rax 17: 48 31 d6 xor rsi,rdx 1a: 48 b9 7f 7f 7f 7f 7f movabs rcx,0x7f7f7f7f7f7f7f7f 21: 7f 7f 7f 24: 48 21 d1 and rcx,rdx 27: 48 ba 7f 7f 7f 7f 7f movabs rdx,0x7f7f7f7f7f7f7f7f 2e: 7f 7f 7f 31: 48 21 d0 and rax,rdx 34: 48 ba 80 80 80 80 80 movabs rdx,0x8080808080808080 3b: 80 80 80 3e: 48 21 f2 and rdx,rsi 41: 48 01 c8 add rax,rcx 44: 48 89 d6 mov rsi,rdx 47: 48 31 c6 xor rsi,rax 4a: 48 8b 45 18 mov rax,QWORD PTR [rbp+0x18] 4e: 48 8b 55 28 mov rdx,QWORD PTR [rbp+0x28] 52: 48 89 c7 mov rdi,rax 55: 48 31 d7 xor rdi,rdx 58: 48 b9 7f 7f 7f 7f 7f movabs rcx,0x7f7f7f7f7f7f7f7f 5f: 7f 7f 7f 62: 48 21 d1 and rcx,rdx 65: 48 ba 7f 7f 7f 7f 7f movabs rdx,0x7f7f7f7f7f7f7f7f 6c: 7f 7f 7f 6f: 48 21 d0 and rax,rdx 72: 48 ba 80 80 80 80 80 movabs rdx,0x8080808080808080 79: 80 80 80 7c: 48 21 fa and rdx,rdi 7f: 48 01 c8 add rax,rcx 82: 48 89 d1 mov rcx,rdx 85: 48 31 c1 xor rcx,rax 88: b8 00 00 00 00 mov eax,0x0 8d: ba 00 00 00 00 mov edx,0x0 92: 48 89 f0 mov rax,rsi 95: 48 89 ca mov rdx,rcx 98: 48 8b 4d f8 mov rcx,QWORD PTR [rbp-0x8] 9c: 48 89 01 mov QWORD PTR [rcx],rax 9f: 48 89 51 08 mov QWORD PTR [rcx+0x8],rdx
Nás však zajímají „vektorové“ výpočty.
Součet vektorů s prvky typu unsigned char zajišťuje instrukce PADDB:
8: 0f 29 45 f0 movaps XMMWORD PTR [rbp-0x10],xmm0 c: 0f 29 4d e0 movaps XMMWORD PTR [rbp-0x20],xmm1 10: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi *z = x + y; 14: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 19: 66 0f fc 45 e0 paddb xmm0,XMMWORD PTR [rbp-0x20] 1e: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 22: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Součet vektorů s prvky typu unsigned short (neboli v assembleru „word“) zajišťuje instrukce PADDW:
30: 0f 29 45 f0 movaps XMMWORD PTR [rbp-0x10],xmm0 34: 0f 29 4d e0 movaps XMMWORD PTR [rbp-0x20],xmm1 38: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi *z = x + y; 3c: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 41: 66 0f fd 45 e0 paddw xmm0,XMMWORD PTR [rbp-0x20] 46: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 4a: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Součet po 32bitových „dvouslovech“ („double word“) je vykonán instrukcí PADDD:
58: 0f 29 45 f0 movaps XMMWORD PTR [rbp-0x10],xmm0 5c: 0f 29 4d e0 movaps XMMWORD PTR [rbp-0x20],xmm1 60: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi *z = x + y; 64: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 69: 66 0f fe 45 e0 paddd xmm0,XMMWORD PTR [rbp-0x20] 6e: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 72: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
A konečně součet po „čtyřslovech“ („quad word“) je realizován instrukcí PADDQ:
80: 0f 29 45 f0 movaps XMMWORD PTR [rbp-0x10],xmm0 84: 0f 29 4d e0 movaps XMMWORD PTR [rbp-0x20],xmm1 88: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi *z = x + y; 8c: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 91: 66 0f d4 45 e0 paddq xmm0,XMMWORD PTR [rbp-0x20] 96: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 9a: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
5. Tytéž operace, ovšem prováděné s prvky se znaménkem
V této kapitole si ukážeme podobný příklad, jako byl ten z předchozí kapitoly, nyní ovšem s vektory, jejichž prvky jsou typu signed char/short/int/long:
#include <stdio.h> typedef signed char v16sb __attribute__((vector_size(16))); void add16ub(v16sb x, v16sb y, v16sb * z) { *z = x + y; } typedef signed short v16ss __attribute__((vector_size(16))); void add16us(v16ss x, v16ss y, v16ss * z) { *z = x + y; } typedef signed int v16si __attribute__((vector_size(16))); void add16ui(v16si x, v16si y, v16si * z) { *z = x + y; } typedef signed long int v16sl __attribute__((vector_size(16))); void add16ul(v16sl x, v16sl y, v16sl * z) { *z = x + y; } int main(void) { { v16sb x = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; v16sb y = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; v16sb z; add16ub(x, y, z); int i; puts("vector of signed chars"); for (i = 0; i < sizeof(v16sb) / sizeof(signed char); i++) { printf("%d %d\n", i, z[i]); } } putchar('\n'); { v16ss x = { 0, 1, 2, 3, 4, 5, 6, 7 }; v16ss y = { 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff }; v16ss z; add16us(x, y, z); int i; puts("vector of signed short ints"); for (i = 0; i < sizeof(v16ss) / sizeof(signed short); i++) { printf("%d %d\n", i, z[i]); } } putchar('\n'); { v16si x = { 0, 1, 2, 3 }; v16si y = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; v16si z; add16ui(x, y, z); int i; puts("vector of signed ints"); for (i = 0; i < sizeof(v16si) / sizeof(signed int); i++) { printf("%d %d\n", i, z[i]); } } putchar('\n'); { v16sl x = { 0, 1 }; v16sl y = { 0xffffffffffffffff, 0xffffffffffffffff }; v16sl z; add16ul(x, y, z); int i; puts("vector of signed longs"); for (i = 0; i < sizeof(v16sl) / sizeof(signed long); i++) { printf("%d %ld\n", i, z[i]); } } return 0; }
Výsledky všech čtyř vektorových operací vypadají následovně:
vector of signed chars 0 -1 1 0 2 1 3 2 4 3 5 4 6 5 7 6 8 7 9 8 10 9 11 10 12 11 13 12 14 13 15 14 vector of signed short ints 0 -1 1 0 2 1 3 2 4 3 5 4 6 5 7 6 vector of signed ints 0 -1 1 0 2 1 3 2 vector of signed longs 0 -1 1 0
6. Způsob překladu vektorových operací do assembleru
Vytištěné výsledky jsou sice značně odlišné (ostatně podle očekávání), ale jak se odlišuje sekvence instrukcí získaná přeložením těchto příkladů.
„Nevektorová varianta“:
*z = x + y; c: 48 8b 45 10 mov rax,QWORD PTR [rbp+0x10] 10: 48 8b 55 20 mov rdx,QWORD PTR [rbp+0x20] 14: 48 89 c6 mov rsi,rax 17: 48 31 d6 xor rsi,rdx 1a: 48 b9 7f 7f 7f 7f 7f movabs rcx,0x7f7f7f7f7f7f7f7f 21: 7f 7f 7f 24: 48 21 d1 and rcx,rdx 27: 48 ba 7f 7f 7f 7f 7f movabs rdx,0x7f7f7f7f7f7f7f7f 2e: 7f 7f 7f 31: 48 21 d0 and rax,rdx 34: 48 ba 80 80 80 80 80 movabs rdx,0x8080808080808080 3b: 80 80 80 3e: 48 21 f2 and rdx,rsi 41: 48 01 c8 add rax,rcx 44: 48 89 d6 mov rsi,rdx 47: 48 31 c6 xor rsi,rax 4a: 48 8b 45 18 mov rax,QWORD PTR [rbp+0x18] 4e: 48 8b 55 28 mov rdx,QWORD PTR [rbp+0x28] 52: 48 89 c7 mov rdi,rax 55: 48 31 d7 xor rdi,rdx 58: 48 b9 7f 7f 7f 7f 7f movabs rcx,0x7f7f7f7f7f7f7f7f 5f: 7f 7f 7f 62: 48 21 d1 and rcx,rdx 65: 48 ba 7f 7f 7f 7f 7f movabs rdx,0x7f7f7f7f7f7f7f7f 6c: 7f 7f 7f 6f: 48 21 d0 and rax,rdx 72: 48 ba 80 80 80 80 80 movabs rdx,0x8080808080808080 79: 80 80 80 7c: 48 21 fa and rdx,rdi 7f: 48 01 c8 add rax,rcx 82: 48 89 d1 mov rcx,rdx 85: 48 31 c1 xor rcx,rax 88: b8 00 00 00 00 mov eax,0x0 8d: ba 00 00 00 00 mov edx,0x0 92: 48 89 f0 mov rax,rsi 95: 48 89 ca mov rdx,rcx 98: 48 8b 4d f8 mov rcx,QWORD PTR [rbp-0x8] 9c: 48 89 01 mov QWORD PTR [rcx],rax 9f: 48 89 51 08 mov QWORD PTR [rcx+0x8],rdx
Součet vektorů s prvky typu signed char:
8: 0f 29 45 f0 movaps XMMWORD PTR [rbp-0x10],xmm0 c: 0f 29 4d e0 movaps XMMWORD PTR [rbp-0x20],xmm1 10: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi *z = x + y; 14: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 19: 66 0f fc 45 e0 paddb xmm0,XMMWORD PTR [rbp-0x20] 1e: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 22: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Součet vektorů s prvky typu signed short:
30: 0f 29 45 f0 movaps XMMWORD PTR [rbp-0x10],xmm0 34: 0f 29 4d e0 movaps XMMWORD PTR [rbp-0x20],xmm1 38: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi *z = x + y; 3c: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 41: 66 0f fd 45 e0 paddw xmm0,XMMWORD PTR [rbp-0x20] 46: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 4a: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Součet vektorů s prvky typu signed int:
58: 0f 29 45 f0 movaps XMMWORD PTR [rbp-0x10],xmm0 5c: 0f 29 4d e0 movaps XMMWORD PTR [rbp-0x20],xmm1 60: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi *z = x + y; 64: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 69: 66 0f fe 45 e0 paddd xmm0,XMMWORD PTR [rbp-0x20] 6e: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 72: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
A konečně součet vektorů s prvky typu signed long:
80: 0f 29 45 f0 movaps XMMWORD PTR [rbp-0x10],xmm0 84: 0f 29 4d e0 movaps XMMWORD PTR [rbp-0x20],xmm1 88: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi *z = x + y; 8c: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 91: 66 0f d4 45 e0 paddq xmm0,XMMWORD PTR [rbp-0x20] 96: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 9a: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
7. Vektorové operace nad vektory s prvky typu float a double
Zbývá nám zjistit, jakým způsobem se přeloží kód s vektory, jejichž prvky jsou typu float a double:
#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; }
Výsledky získané po překladu a spuštění tohoto jednoduchého demonstračního příkladu:
vector of floats 0 0.100000 1 1.100000 2 2.100000 3 3.100000 vector of doubles 0 0.100000 1 1.100000
8. Překlad do assembleru
Pokud jsou SIMD instrukce při překladu zakázány, jsou vektory sčítány prvek po prvku, zde konkrétně s využitím instrukce matematického koprocesoru FADDP. Pro původní prvky typu float se musí provést čtyři součty:
*z = x + y; c: d9 45 10 fld DWORD PTR [rbp+0x10] f: d9 45 20 fld DWORD PTR [rbp+0x20] 12: de c1 faddp st(1),st 14: d9 45 14 fld DWORD PTR [rbp+0x14] 17: d9 45 24 fld DWORD PTR [rbp+0x24] 1a: de c1 faddp st(1),st 1c: d9 45 18 fld DWORD PTR [rbp+0x18] 1f: d9 45 28 fld DWORD PTR [rbp+0x28] 22: de c1 faddp st(1),st 24: d9 45 1c fld DWORD PTR [rbp+0x1c] 27: d9 45 2c fld DWORD PTR [rbp+0x2c] 2a: de c1 faddp st(1),st 2c: d9 cb fxch st(3) 2e: d9 5d d0 fstp DWORD PTR [rbp-0x30] 31: d9 c9 fxch st(1) 33: d9 5d d4 fstp DWORD PTR [rbp-0x2c] 36: d9 5d d8 fstp DWORD PTR [rbp-0x28] 39: d9 5d dc fstp DWORD PTR [rbp-0x24] 3c: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30] 40: 48 89 45 f0 mov QWORD PTR [rbp-0x10],rax 44: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 48: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax 4c: 48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18] 50: 48 8b 55 f0 mov rdx,QWORD PTR [rbp-0x10] 54: 48 89 10 mov QWORD PTR [rax],rdx 57: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8] 5b: 48 89 50 08 mov QWORD PTR [rax+0x8],rdx
Pro prvky vektorů typu double se použijí pouze dvě operace součtu, protože celková délka vektoru je 2×sizeof(double)=16 bajtů:
6e: dd 45 10 fld QWORD PTR [rbp+0x10] 71: dd 45 20 fld QWORD PTR [rbp+0x20] 74: de c1 faddp st(1),st 76: dd 45 18 fld QWORD PTR [rbp+0x18] 79: dd 45 28 fld QWORD PTR [rbp+0x28] 7c: de c1 faddp st(1),st 7e: d9 c9 fxch st(1) 80: dd 5d d0 fstp QWORD PTR [rbp-0x30] 83: dd 5d d8 fstp QWORD PTR [rbp-0x28] 86: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30] 8a: 48 89 45 f0 mov QWORD PTR [rbp-0x10],rax 8e: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 92: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax 96: 48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18] 9a: 48 8b 55 f0 mov rdx,QWORD PTR [rbp-0x10] 9e: 48 89 10 mov QWORD PTR [rax],rdx a1: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8] a5: 48 89 50 08 mov QWORD PTR [rax+0x8],rdx
Pro nás je dnes pochopitelně mnohem zajímavější překlad využívající SIMD instrukce. Součet prvků vektoru typu float je realizován takto – instrukcí ADDPS (viz též úvodní článek):
*z = x + y; 14: 0f 28 45 f0 movaps xmm0,XMMWORD PTR [rbp-0x10] 18: 0f 58 45 e0 addps xmm0,XMMWORD PTR [rbp-0x20] 1c: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 20: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
A pro vektory s prvky typu double vypadá jejich součet následovně – je realizován instrukcí ADDPD:
3a: 66 0f 28 45 f0 movapd xmm0,XMMWORD PTR [rbp-0x10] 3f: 66 0f 58 45 e0 addpd xmm0,XMMWORD PTR [rbp-0x20] 44: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 48: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
9. Vektor není libovolně velké pole – omezení GCC
Z předchozího textu by se mohlo zdát, že vektory zavedené v rámci rozšíření GCC jsou vlastně určitou formou polí a že je možné nebo vhodné nahradit pole (předem známé délky) právě za vektory, bez ohledu na jejich velikost. Teoreticky to samozřejmě možné je, ale výsledná sekvence instrukcí vygenerovaná překladačem nás může nepříjemně překvapit, zejména ve chvíli, kdy se například pokusíme vytvořit vektor se všemi pixely obrázku atd.
Vyzkoušejme si to na příkladu, v němž je použit vektor s délkou 1024 bajtů (což je vlastně s ohledem na další možné varianty ještě velmi krátký vektor):
typedef float v1024f __attribute__((vector_size(1024))); void addVectors(v1024f * x, v1024f * y, v1024f * z) { *z = *x + *y; } int main(void) { v1024f x = { 1.0 }; v1024f y = { 1.0 }; v1024f z; addVectors( x, y, z); return 0; }
10. Způsob překladu operace s dlouhým vektorem do assembleru
Nyní se můžeme podívat na výsledek překladu předchozího demonstračního příkladu v případě, že je překlad proveden s vypnutím SIMD instrukcí (zde konkrétně se použil přepínač -mno-sse). Přeložená funkce je v tomto případě obrovská, protože strojový kód přesahuje pět kilobajtů. Nejprve můžeme vidět dva blokové přesuny dat, přičemž každý blok má velikost 1024 bajtů (přesouvá se 128×8 bajtů). Tyto přesuny jsou realizovány instrukcí movs s prefixem rep:
3a: 48 8b 94 24 80 0b 00 mov rdx,QWORD PTR [rsp+0xb80] 41: 00 42: 48 8d 84 24 88 0b 00 lea rax,[rsp+0xb88] 49: 00 4a: 48 89 d6 mov rsi,rdx 4d: ba 80 00 00 00 mov edx,0x80 52: 48 89 c7 mov rdi,rax 55: 48 89 d1 mov rcx,rdx 58: f3 48 a5 rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi] 5b: 48 8b 94 24 78 0b 00 mov rdx,QWORD PTR [rsp+0xb78] 62: 00 63: 48 8d 84 24 88 0f 00 lea rax,[rsp+0xf88] 6a: 00 6b: 48 89 d6 mov rsi,rdx 6e: ba 80 00 00 00 mov edx,0x80 73: 48 89 c7 mov rdi,rax 76: 48 89 d1 mov rcx,rdx 79: f3 48 a5 rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
Dále můžeme v kódu vidět opakující se sekvenci čtveřice instrukcí fld+fld+faddp+fstp, což znamená, že se na zásobník ST uloží dvojice operandů typu float, ty se sečtou s uložením výsledku zpět na zásobník a nakonec se výsledek uloží instrukcí fstp (písmeno „p“ na konci znamená „pop“ a nikoli „parallel“, jako je tomu v SSE):
7c: d9 84 24 88 0b 00 00 fld DWORD PTR [rsp+0xb88] 83: d9 84 24 88 0f 00 00 fld DWORD PTR [rsp+0xf88] 8a: de c1 faddp st(1),st 8c: d9 9c 24 64 07 00 00 fstp DWORD PTR [rsp+0x764]
93: d9 84 24 8c 0b 00 00 fld DWORD PTR [rsp+0xb8c] 9a: d9 84 24 8c 0f 00 00 fld DWORD PTR [rsp+0xf8c] a1: de c1 faddp st(1),st a3: d9 9c 24 60 07 00 00 fstp DWORD PTR [rsp+0x760]
aa: d9 84 24 90 0b 00 00 fld DWORD PTR [rsp+0xb90] b1: d9 84 24 90 0f 00 00 fld DWORD PTR [rsp+0xf90] b8: de c1 faddp st(1),st ba: d9 9c 24 5c 07 00 00 fstp DWORD PTR [rsp+0x75c]
Tato sekvence se opakuje 256× (!), což přesně odpovídá počtu prvků vektoru. Na konci je sekvence instrukcí poněkud odlišná, neboť výsledky zůstávají na zásobníku (pro osm prvků):
171b: d9 84 24 7c 0f 00 00 fld DWORD PTR [rsp+0xf7c] 1722: d9 84 24 7c 13 00 00 fld DWORD PTR [rsp+0x137c] 1729: de c1 faddp st(1),st 172b: d9 84 24 80 0f 00 00 fld DWORD PTR [rsp+0xf80] 1732: d9 84 24 80 13 00 00 fld DWORD PTR [rsp+0x1380] 1739: de c1 faddp st(1),st 173b: d9 84 24 84 0f 00 00 fld DWORD PTR [rsp+0xf84] 1742: d9 84 24 84 13 00 00 fld DWORD PTR [rsp+0x1384] 1749: de c1 faddp st(1),st 174b: d9 84 24 64 07 00 00 fld DWORD PTR [rsp+0x764] 1752: d9 9c 24 68 07 00 00 fstp DWORD PTR [rsp+0x768]
Při zapnutí optimalizací dostaneme nepatrně lepší kód, v němž se na zásobník ukládá pouze jeden z operandů. Ono opakování výpočtu 256× je zde stále použito, stejně jako oba blokové přesuny na začátku:
4f: d9 84 24 88 03 00 00 fld DWORD PTR [rsp+0x388] 56: d8 84 24 88 07 00 00 fadd DWORD PTR [rsp+0x788] 5d: d9 5c 24 88 fstp DWORD PTR [rsp-0x78] 61: d9 84 24 8c 03 00 00 fld DWORD PTR [rsp+0x38c] 68: d8 84 24 8c 07 00 00 fadd DWORD PTR [rsp+0x78c] 6f: d9 5c 24 8c fstp DWORD PTR [rsp-0x74]
až:
147f: d9 84 24 84 07 00 00 fld DWORD PTR [rsp+0x784] 1486: d8 84 24 84 0b 00 00 fadd DWORD PTR [rsp+0xb84] 148d: d9 9c 24 84 03 00 00 fstp DWORD PTR [rsp+0x384]
Mohlo by se zdát, že po povolení SIMD operací (zde to bude SSE) se situace značně zlepší. Ovšem i zde můžeme na začátku vidět dvojici blokových přenosů o celkové velikosti dvou kilobajtů:
3a: 48 8b 94 24 80 07 00 mov rdx,QWORD PTR [rsp+0x780] 41: 00 42: 48 8d 84 24 88 07 00 lea rax,[rsp+0x788] 49: 00 4a: 48 89 d6 mov rsi,rdx 4d: ba 80 00 00 00 mov edx,0x80 52: 48 89 c7 mov rdi,rax 55: 48 89 d1 mov rcx,rdx 58: f3 48 a5 rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi] 5b: 48 8b 94 24 78 07 00 mov rdx,QWORD PTR [rsp+0x778] 62: 00 63: 48 8d 84 24 88 0b 00 lea rax,[rsp+0xb88] 6a: 00 6b: 48 89 d6 mov rsi,rdx 6e: ba 80 00 00 00 mov edx,0x80 73: 48 89 c7 mov rdi,rax 76: 48 89 d1 mov rcx,rdx 79: f3 48 a5 rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
Samotný výpočet je realizován čtveřicí instrukcí movaps+movaps+addps+movaps:
7c: 0f 28 8c 24 88 07 00 movaps xmm1,XMMWORD PTR [rsp+0x788] 83: 00 84: 0f 28 84 24 88 0b 00 movaps xmm0,XMMWORD PTR [rsp+0xb88] 8b: 00 8c: 0f 58 c8 addps xmm1,xmm0 8f: 0f 29 8c 24 58 03 00 movaps XMMWORD PTR [rsp+0x358],xmm1
Jedná se o opakující se sekvenci instrukcí, která končí takto:
78e: 0f 28 8c 24 58 0b 00 movaps xmm1,XMMWORD PTR [rsp+0xb58] 795: 00 796: 0f 28 84 24 58 0f 00 movaps xmm0,XMMWORD PTR [rsp+0xf58] 79d: 00 79e: 0f 28 d9 movaps xmm3,xmm1 7a1: 0f 58 d8 addps xmm3,xmm0 7a4: 0f 28 8c 24 68 0b 00 movaps xmm1,XMMWORD PTR [rsp+0xb68] 7ab: 00 7ac: 0f 28 84 24 68 0f 00 movaps xmm0,XMMWORD PTR [rsp+0xf68]
Po optimalizaci získáme kratší kód. Celý výpočet začíná následovně:
4f: 0f 28 84 24 88 03 00 movaps xmm0,XMMWORD PTR [rsp+0x388] 56: 00 57: 0f 58 84 24 88 07 00 addps xmm0,XMMWORD PTR [rsp+0x788] 5e: 00 5f: 0f 29 44 24 88 movaps XMMWORD PTR [rsp-0x78],xmm0
…
64: 0f 28 84 24 98 03 00 movaps xmm0,XMMWORD PTR [rsp+0x398] 6b: 00 6c: 0f 58 84 24 98 07 00 addps xmm0,XMMWORD PTR [rsp+0x798] 73: 00 74: 0f 29 44 24 98 movaps XMMWORD PTR [rsp-0x68],xmm0
a končí takto:
607: 0f 28 84 24 78 07 00 movaps xmm0,XMMWORD PTR [rsp+0x778] 60e: 00 60f: 0f 58 84 24 78 0b 00 addps xmm0,XMMWORD PTR [rsp+0xb78] 616: 00 617: 0f 29 84 24 78 03 00 movaps XMMWORD PTR [rsp+0x378],xmm0
typedef float v1024f __attribute__((vector_size(65536))); void addVectors(v1024f * x, v1024f * y, v1024f * z) { *z = *x + *y; } int main(void) { v1024f x = { 1.0 }; v1024f y = { 1.0 }; v1024f z; addVectors(&x, &y, &z); return 0; }
Výsledkem překladu bude tento absurdně velký objektový kód:
$ ls -lh simd16_1.o -rw-rw-r-- 1 ptisnovs ptisnovs 193K Oct 9 09:23 simd16B.o
11. Ne všechny operace mohou být v SIMD podporovány
Rozšíření GCC umožňuje provádět následující operace s prvky celočíselných vektorů (předpokládá se použití dvojice vektorů stejné délky):
+, -, *, /, unární minus, ^, |, &, ~, %, << a >>
Pokud by se jednalo o prvky typu float nebo double, jsou povoleny tyto operace:
+, -, *, / a unární minus
Ovšem samotné rozšíření GCC žádným způsobem nespecifikuje, jakým způsobem se vlastně „vektorové operace“ přeloží do výsledného strojového kódu. Můžeme si to otestovat na následujícím demonstračním příkladu, v němž jsou použity všechny podporované binární operace pro celočíselné prvky vektorů:
#include <stdio.h> typedef signed char v16ib __attribute__((vector_size(16))); void add16ib(v16ib x, v16ib y, v16ib * z) { *z = x + y; } void sub16ib(v16ib x, v16ib y, v16ib * z) { *z = x - y; } void mul16ib(v16ib x, v16ib y, v16ib * z) { *z = x * y; } void div16ib(v16ib x, v16ib y, v16ib * z) { *z = x / y; } void mod16ib(v16ib x, v16ib y, v16ib * z) { *z = x % y; } void and16ib(v16ib x, v16ib y, v16ib * z) { *z = x & y; } void or16ib(v16ib x, v16ib y, v16ib * z) { *z = x | y; } void xor16ib(v16ib x, v16ib y, v16ib * z) { *z = x ^ y; } void rshift16ib(v16ib x, v16ib y, v16ib * z) { *z = x >> y; } void lshift16ib(v16ib x, v16ib y, v16ib * z) { *z = x << y; } void print_vectors(const char *message, const char *op, v16ib *x,v16ib *y,v16ib *z) { int i; puts(message); for (i = 0; i < sizeof(v16ib) / sizeof(signed char); i++) { printf("%2d %d %s %d = %d\n", i, (*x)[i], op, (*y)[i], (*z)[i]); } putchar('\n'); } int main(void) { v16ib x; v16ib y; v16ib z; int i; for (i = 0; i < sizeof(v16ib) / sizeof(signed char); i++) { x[i] = i*2; y[i] = 16-i; } add16ib(x, y, &z); print_vectors("vector addition", "+", &x, &y, &z); sub16ib(x, y, &z); print_vectors("vector subtraction", "-", &x, &y, &z); mul16ib(x, y, &z); print_vectors("vector multiply", "*", &x, &y, &z); div16ib(x, y, &z); print_vectors("vector divide", "/", &x, &y, &z); mod16ib(x, y, &z); print_vectors("vector modulo", "%", &x, &y, &z); and16ib(x, y, &z); print_vectors("vector bitwise and", "&", &x, &y, &z); or16ib(x, y, &z); print_vectors("vector bitwise or", "|", &x, &y, &z); xor16ib(x, y, &z); print_vectors("vector bitwise xor", "^", &x, &y, &z); rshift16ib(x, y, &z); print_vectors("vector right shift", ">>", &x, &y, &z); lshift16ib(x, y, &z); print_vectors("vector left shift", "<<", &x, &y, &z); return 0; }
S těmito výsledky:
vector addition 0 0 + 16 = 16 1 2 + 15 = 17 2 4 + 14 = 18 3 6 + 13 = 19 4 8 + 12 = 20 5 10 + 11 = 21 6 12 + 10 = 22 7 14 + 9 = 23 8 16 + 8 = 24 9 18 + 7 = 25 10 20 + 6 = 26 11 22 + 5 = 27 12 24 + 4 = 28 13 26 + 3 = 29 14 28 + 2 = 30 15 30 + 1 = 31 vector subtraction 0 0 - 16 = -16 1 2 - 15 = -13 2 4 - 14 = -10 3 6 - 13 = -7 4 8 - 12 = -4 5 10 - 11 = -1 6 12 - 10 = 2 7 14 - 9 = 5 8 16 - 8 = 8 9 18 - 7 = 11 10 20 - 6 = 14 11 22 - 5 = 17 12 24 - 4 = 20 13 26 - 3 = 23 14 28 - 2 = 26 15 30 - 1 = 29 vector multiply 0 0 * 16 = 0 1 2 * 15 = 30 2 4 * 14 = 56 3 6 * 13 = 78 4 8 * 12 = 96 5 10 * 11 = 110 6 12 * 10 = 120 7 14 * 9 = 126 8 16 * 8 = -128 9 18 * 7 = 126 10 20 * 6 = 120 11 22 * 5 = 110 12 24 * 4 = 96 13 26 * 3 = 78 14 28 * 2 = 56 15 30 * 1 = 30 vector divide 0 0 / 16 = 0 1 2 / 15 = 0 2 4 / 14 = 0 3 6 / 13 = 0 4 8 / 12 = 0 5 10 / 11 = 0 6 12 / 10 = 1 7 14 / 9 = 1 8 16 / 8 = 2 9 18 / 7 = 2 10 20 / 6 = 3 11 22 / 5 = 4 12 24 / 4 = 6 13 26 / 3 = 8 14 28 / 2 = 14 15 30 / 1 = 30 vector modulo 0 0 % 16 = 0 1 2 % 15 = 2 2 4 % 14 = 4 3 6 % 13 = 6 4 8 % 12 = 8 5 10 % 11 = 10 6 12 % 10 = 2 7 14 % 9 = 5 8 16 % 8 = 0 9 18 % 7 = 4 10 20 % 6 = 2 11 22 % 5 = 2 12 24 % 4 = 0 13 26 % 3 = 2 14 28 % 2 = 0 15 30 % 1 = 0 vector bitwise and 0 0 & 16 = 0 1 2 & 15 = 2 2 4 & 14 = 4 3 6 & 13 = 4 4 8 & 12 = 8 5 10 & 11 = 10 6 12 & 10 = 8 7 14 & 9 = 8 8 16 & 8 = 0 9 18 & 7 = 2 10 20 & 6 = 4 11 22 & 5 = 4 12 24 & 4 = 0 13 26 & 3 = 2 14 28 & 2 = 0 15 30 & 1 = 0 vector bitwise or 0 0 | 16 = 16 1 2 | 15 = 15 2 4 | 14 = 14 3 6 | 13 = 15 4 8 | 12 = 12 5 10 | 11 = 11 6 12 | 10 = 14 7 14 | 9 = 15 8 16 | 8 = 24 9 18 | 7 = 23 10 20 | 6 = 22 11 22 | 5 = 23 12 24 | 4 = 28 13 26 | 3 = 27 14 28 | 2 = 30 15 30 | 1 = 31 vector bitwise xor 0 0 ^ 16 = 16 1 2 ^ 15 = 13 2 4 ^ 14 = 10 3 6 ^ 13 = 11 4 8 ^ 12 = 4 5 10 ^ 11 = 1 6 12 ^ 10 = 6 7 14 ^ 9 = 7 8 16 ^ 8 = 24 9 18 ^ 7 = 21 10 20 ^ 6 = 18 11 22 ^ 5 = 19 12 24 ^ 4 = 28 13 26 ^ 3 = 25 14 28 ^ 2 = 30 15 30 ^ 1 = 31 vector right shift 0 0 >> 16 = 0 1 2 >> 15 = 0 2 4 >> 14 = 0 3 6 >> 13 = 0 4 8 >> 12 = 0 5 10 >> 11 = 0 6 12 >> 10 = 0 7 14 >> 9 = 0 8 16 >> 8 = 0 9 18 >> 7 = 0 10 20 >> 6 = 0 11 22 >> 5 = 0 12 24 >> 4 = 1 13 26 >> 3 = 3 14 28 >> 2 = 7 15 30 >> 1 = 15 vector left shift 0 0 << 16 = 0 1 2 << 15 = 0 2 4 << 14 = 0 3 6 << 13 = 0 4 8 << 12 = 0 5 10 << 11 = 0 6 12 << 10 = 0 7 14 << 9 = 0 8 16 << 8 = 0 9 18 << 7 = 0 10 20 << 6 = 0 11 22 << 5 = -64 12 24 << 4 = -128 13 26 << 3 = -48 14 28 << 2 = 112 15 30 << 1 = 60
12. Výsledek překladu předchozího programu do kódu se SIMD operacemi
Podívejme se nyní postupně na způsob překladu předchozího programu ve chvíli, kdy jsou při překladu povoleny SIMD operace. Pro stručnost si uvedeme jen „výpočetní část“ každé z funkcí, tedy bez přípravy operandů a bez úklidu zásobníku na konci každé funkce.
Vektorový součet je realizován jedinou instrukcí PADDB:
14: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 19: 66 0f fc 45 e0 paddb xmm0,XMMWORD PTR [rbp-0x20] 1e: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 22: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Vektorový rozdíl je realizován jedinou instrukcí PSUBB:
3c: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 41: 66 0f f8 45 e0 psubb xmm0,XMMWORD PTR [rbp-0x20] 46: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 4a: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Součin prvků vektorů již není tak triviální, jak by se možná mohlo na první pohled zdát, neboť je nutné zajistit, aby každý výsledný prvek měl opět pouze osm bitů výsledku a nikoli šestnáct bitů. Nejdříve se prvky vektorů instrukcemi PUNPCKLBW a PUNPCKHBW rozdělí na vektory šestnáctibitových slov, provede se výpočet instrukcí PMULLW (tedy skutečně násobení šestnáctibitových hodnot) a poté se výsledky opět „zkomprimují“ instrukcí PACKUSWB
64: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 69: 66 0f 6f 4d e0 movdqa xmm1,XMMWORD PTR [rbp-0x20] 6e: 66 0f 6f d9 movdqa xmm3,xmm1 72: 66 0f 60 d9 punpcklbw xmm3,xmm1 76: 66 0f 6f d1 movdqa xmm2,xmm1 7a: 66 0f 68 d1 punpckhbw xmm2,xmm1 7e: 66 0f 6f c8 movdqa xmm1,xmm0 82: 66 0f 60 c8 punpcklbw xmm1,xmm0 86: 66 0f 68 c0 punpckhbw xmm0,xmm0 8a: 66 0f 6f e1 movdqa xmm4,xmm1 8e: 66 0f 6f cb movdqa xmm1,xmm3 92: 66 0f d5 cc pmullw xmm1,xmm4 96: 66 0f 6f d8 movdqa xmm3,xmm0 9a: 66 0f 6f c2 movdqa xmm0,xmm2 9e: 66 0f 6f d3 movdqa xmm2,xmm3 a2: 66 0f d5 d0 pmullw xmm2,xmm0 a6: 66 0f 6f 05 00 00 00 movdqa xmm0,XMMWORD PTR [rip+0x0] ad: 00 ae: 66 0f db c8 pand xmm1,xmm0 b2: 66 0f db c2 pand xmm0,xmm2 b6: 66 0f 6f e9 movdqa xmm5,xmm1 ba: 66 0f 67 e8 packuswb xmm5,xmm0 be: 66 0f 6f c5 movdqa xmm0,xmm5 c2: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] c6: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Další operací je podíl prvků vektorů, který je složitý a pomalý, protože je realizován sekvenčně, konkrétně standardní instrukcí IDIV (!), předtím je ještě nutné operand převést z bajtu na 16bitové slovo instrukcí CBW (uff):
e9: 0f b6 45 c0 movzx eax,BYTE PTR [rbp-0x40] ed: 0f b6 55 b0 movzx edx,BYTE PTR [rbp-0x50] f1: 66 98 cbw f3: f6 fa idiv dl f5: 41 89 c7 mov r15d,eax f8: 0f b6 45 c1 movzx eax,BYTE PTR [rbp-0x3f] fc: 0f b6 55 b1 movzx edx,BYTE PTR [rbp-0x4f] 100: 66 98 cbw 102: f6 fa idiv dl 104: 88 45 90 mov BYTE PTR [rbp-0x70],al 107: 0f b6 45 c2 movzx eax,BYTE PTR [rbp-0x3e] 10b: 0f b6 55 b2 movzx edx,BYTE PTR [rbp-0x4e] 10f: 66 98 cbw 111: f6 fa idiv dl 113: 88 45 a7 mov BYTE PTR [rbp-0x59],al 116: 0f b6 45 c3 movzx eax,BYTE PTR [rbp-0x3d] 11a: 0f b6 55 b3 movzx edx,BYTE PTR [rbp-0x4d] 11e: 66 98 cbw 120: f6 fa idiv dl 122: 88 45 a6 mov BYTE PTR [rbp-0x5a],al 125: 0f b6 45 c4 movzx eax,BYTE PTR [rbp-0x3c] 129: 0f b6 55 b4 movzx edx,BYTE PTR [rbp-0x4c] 12d: 66 98 cbw 12f: f6 fa idiv dl 131: 88 45 a5 mov BYTE PTR [rbp-0x5b],al 134: 0f b6 45 c5 movzx eax,BYTE PTR [rbp-0x3b] 138: 0f b6 55 b5 movzx edx,BYTE PTR [rbp-0x4b] 13c: 66 98 cbw 13e: f6 fa idiv dl 140: 41 89 c2 mov r10d,eax 143: 0f b6 45 c6 movzx eax,BYTE PTR [rbp-0x3a] 147: 0f b6 55 b6 movzx edx,BYTE PTR [rbp-0x4a] 14b: 66 98 cbw 14d: f6 fa idiv dl 14f: 41 89 c0 mov r8d,eax 152: 0f b6 45 c7 movzx eax,BYTE PTR [rbp-0x39] 156: 0f b6 55 b7 movzx edx,BYTE PTR [rbp-0x49] 15a: 66 98 cbw 15c: f6 fa idiv dl 15e: 89 c7 mov edi,eax 160: 0f b6 45 c8 movzx eax,BYTE PTR [rbp-0x38] 164: 0f b6 55 b8 movzx edx,BYTE PTR [rbp-0x48] 168: 66 98 cbw 16a: f6 fa idiv dl 16c: 41 89 c3 mov r11d,eax 16f: 0f b6 45 c9 movzx eax,BYTE PTR [rbp-0x37] 173: 0f b6 55 b9 movzx edx,BYTE PTR [rbp-0x47] 177: 66 98 cbw 179: f6 fa idiv dl 17b: 89 c3 mov ebx,eax 17d: 0f b6 45 ca movzx eax,BYTE PTR [rbp-0x36] 181: 0f b6 55 ba movzx edx,BYTE PTR [rbp-0x46] 185: 66 98 cbw 187: f6 fa idiv dl 189: 41 89 c4 mov r12d,eax 18c: 0f b6 45 cb movzx eax,BYTE PTR [rbp-0x35] 190: 0f b6 55 bb movzx edx,BYTE PTR [rbp-0x45] 194: 66 98 cbw 196: f6 fa idiv dl 198: 41 89 c5 mov r13d,eax 19b: 0f b6 45 cc movzx eax,BYTE PTR [rbp-0x34] 19f: 0f b6 55 bc movzx edx,BYTE PTR [rbp-0x44] 1a3: 66 98 cbw 1a5: f6 fa idiv dl 1a7: 41 89 c6 mov r14d,eax 1aa: 0f b6 45 cd movzx eax,BYTE PTR [rbp-0x33] 1ae: 0f b6 55 bd movzx edx,BYTE PTR [rbp-0x43] 1b2: 66 98 cbw 1b4: f6 fa idiv dl 1b6: 41 89 c1 mov r9d,eax 1b9: 0f b6 45 ce movzx eax,BYTE PTR [rbp-0x32] 1bd: 0f b6 55 be movzx edx,BYTE PTR [rbp-0x42] 1c1: 66 98 cbw 1c3: f6 fa idiv dl 1c5: 89 c6 mov esi,eax 1c7: 0f b6 45 cf movzx eax,BYTE PTR [rbp-0x31] 1cb: 0f b6 55 bf movzx edx,BYTE PTR [rbp-0x41] 1cf: 66 98 cbw 1d1: f6 fa idiv dl
Aplikace operátoru & na prvky vektoru s využitím instrukce PAND:
*z = x & y; 4a2: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 4a7: 66 0f db 45 e0 pand xmm0,XMMWORD PTR [rbp-0x20] 4ac: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 4b0: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Aplikace operátoru | na prvky vektoru s využitím instrukce POR:
*z = x | y; 4ca: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 4cf: 66 0f eb 45 e0 por xmm0,XMMWORD PTR [rbp-0x20] 4d4: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 4d8: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Aplikace operátoru ^ na prvky vektoru s využitím instrukce PXOR:
*z = x ^ y; 4f2: 66 0f 6f 45 f0 movdqa xmm0,XMMWORD PTR [rbp-0x10] 4f7: 66 0f ef 45 e0 pxor xmm0,XMMWORD PTR [rbp-0x20] 4fc: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 500: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Překlad funkce, v níž se provádí bitový posun prvků jednoho vektoru na základě hodnot uložených ve druhém vektoru, je stejně tristní, jako tomu bylo u dělení prvků – žádné vektorové (přesněji řečen SIMD) instrukce zde nejsou použity:
523: 0f b6 55 c0 movzx edx,BYTE PTR [rbp-0x40] 527: 0f b6 45 b0 movzx eax,BYTE PTR [rbp-0x50] 52b: 0f be c0 movsx eax,al 52e: 89 c1 mov ecx,eax 530: d2 fa sar dl,cl ... ... ... 634: 0f be c0 movsx eax,al 637: 89 d7 mov edi,edx 639: 89 c1 mov ecx,eax 63b: 40 d2 ff sar dil,cl 63e: 0f b6 55 cf movzx edx,BYTE PTR [rbp-0x31] 642: 0f b6 45 bf movzx eax,BYTE PTR [rbp-0x41]
13. Základní aritmetické operace s prvky vektorů typu float
Nyní se podívejme na to, jakým způsobem se s využitím SIMD operací přeloží následující demonstrační příklad, v němž jsou použity všechny „vektorové operátory“ pro vektory s prvky typu float, tedy s numerickými hodnotami uloženými v systému plovoucí řádové tečky/čárky (těchto operátorů je mnohem méně, v porovnání s celočíselnými prvky vektorů):
#include <stdio.h> typedef float v16float __attribute__((vector_size(16))); void add16float(v16float x, v16float y, v16float * z) { *z = x + y; } void sub16float(v16float x, v16float y, v16float * z) { *z = x - y; } void mul16float(v16float x, v16float y, v16float * z) { *z = x * y; } void div16float(v16float x, v16float y, v16float * z) { *z = x / y; } void print_vectors(const char *message, const char op, v16float * x, v16float * y, v16float * z) { int i; puts(message); for (i = 0; i < sizeof(v16float) / sizeof(float); i++) { printf("%2d %5.3f %c %5.3f = %5.3f\n", i, (*x)[i], op, (*y)[i], (*z)[i]); } putchar('\n'); } int main(void) { v16float x; v16float y; v16float z; int i; for (i = 0; i < sizeof(v16float) / sizeof(float); i++) { x[i] = i; y[i] = i + 0.1; } add16float(x, y, &z); print_vectors("vector addition", '+', &x, &y, &z); sub16float(x, y, &z); print_vectors("vector subtraction", '-', &x, &y, &z); mul16float(x, y, &z); print_vectors("vector multiply", '*', &x, &y, &z); div16float(x, y, &z); print_vectors("vector divide", '/', &x, &y, &z); return 0; }
S výsledky:
vector addition 0 0.000 + 0.100 = 0.100 1 1.000 + 1.100 = 2.100 2 2.000 + 2.100 = 4.100 3 3.000 + 3.100 = 6.100 vector subtraction 0 0.000 - 0.100 = -0.100 1 1.000 - 1.100 = -0.100 2 2.000 - 2.100 = -0.100 3 3.000 - 3.100 = -0.100 vector multiply 0 0.000 * 0.100 = 0.000 1 1.000 * 1.100 = 1.100 2 2.000 * 2.100 = 4.200 3 3.000 * 3.100 = 9.300 vector divide 0 0.000 / 0.100 = 0.000 1 1.000 / 1.100 = 0.909 2 2.000 / 2.100 = 0.952 3 3.000 / 3.100 = 0.968
14. Překlad bez použití instrukcí SIMD
Nejprve se podívejme na způsob překladu tohoto demonstračního příkladu ve chvíli, kdy nejsou povoleny SIMD instrukce (tedy v našem případě SSE). Neoptimalizovaný kód součtu dvou čtyřprvkových vektorů je (nepřekvapivě) založen na instrukci FADDP:
c: d9 45 10 fld DWORD PTR [rbp+0x10] f: d9 45 20 fld DWORD PTR [rbp+0x20] 12: de c1 faddp st(1),st 14: d9 45 14 fld DWORD PTR [rbp+0x14] 17: d9 45 24 fld DWORD PTR [rbp+0x24] 1a: de c1 faddp st(1),st 1c: d9 45 18 fld DWORD PTR [rbp+0x18] 1f: d9 45 28 fld DWORD PTR [rbp+0x28] 22: de c1 faddp st(1),st 24: d9 45 1c fld DWORD PTR [rbp+0x1c] 27: d9 45 2c fld DWORD PTR [rbp+0x2c] 2a: de c1 faddp st(1),st 2c: d9 cb fxch st(3) 2e: d9 5d d0 fstp DWORD PTR [rbp-0x30] 31: d9 c9 fxch st(1) 33: d9 5d d4 fstp DWORD PTR [rbp-0x2c] 36: d9 5d d8 fstp DWORD PTR [rbp-0x28] 39: d9 5d dc fstp DWORD PTR [rbp-0x24] 3c: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30] 40: 48 89 45 f0 mov QWORD PTR [rbp-0x10],rax 44: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 48: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax 4c: 48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18] 50: 48 8b 55 f0 mov rdx,QWORD PTR [rbp-0x10] 54: 48 89 10 mov QWORD PTR [rax],rdx 57: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8] 5b: 48 89 50 08 mov QWORD PTR [rax+0x8],rdx
Po optimalizaci dostaneme tento kód:
4: d9 44 24 08 fld DWORD PTR [rsp+0x8] 8: d8 44 24 18 fadd DWORD PTR [rsp+0x18] c: d9 5c 24 d8 fstp DWORD PTR [rsp-0x28] 10: d9 44 24 0c fld DWORD PTR [rsp+0xc] 14: d8 44 24 1c fadd DWORD PTR [rsp+0x1c] 18: d9 5c 24 dc fstp DWORD PTR [rsp-0x24] 1c: d9 44 24 10 fld DWORD PTR [rsp+0x10] 20: d8 44 24 20 fadd DWORD PTR [rsp+0x20] 24: 48 8b 44 24 d8 mov rax,QWORD PTR [rsp-0x28] 29: 48 89 07 mov QWORD PTR [rdi],rax 2c: d9 5c 24 e0 fstp DWORD PTR [rsp-0x20] 30: d9 44 24 14 fld DWORD PTR [rsp+0x14] 34: d8 44 24 24 fadd DWORD PTR [rsp+0x24] 38: d9 5c 24 e4 fstp DWORD PTR [rsp-0x1c] 3c: 48 8b 44 24 e0 mov rax,QWORD PTR [rsp-0x20] 41: 48 89 47 08 mov QWORD PTR [rdi+0x8],rax
Dělení je realizováno instrukcí FDIV:
f8: d8 74 24 18 fdiv DWORD PTR [rsp+0x18] fc: d9 5c 24 d8 fstp DWORD PTR [rsp-0x28] 100: d9 44 24 0c fld DWORD PTR [rsp+0xc] 104: d8 74 24 1c fdiv DWORD PTR [rsp+0x1c] 108: d9 5c 24 dc fstp DWORD PTR [rsp-0x24] 10c: d9 44 24 10 fld DWORD PTR [rsp+0x10] 110: d8 74 24 20 fdiv DWORD PTR [rsp+0x20] 114: 48 8b 44 24 d8 mov rax,QWORD PTR [rsp-0x28] 119: 48 89 07 mov QWORD PTR [rdi],rax 11c: d9 5c 24 e0 fstp DWORD PTR [rsp-0x20] 120: d9 44 24 14 fld DWORD PTR [rsp+0x14] 124: d8 74 24 24 fdiv DWORD PTR [rsp+0x24] 128: d9 5c 24 e4 fstp DWORD PTR [rsp-0x1c] 12c: 48 8b 44 24 e0 mov rax,QWORD PTR [rsp-0x20] 131: 48 89 47 08 mov QWORD PTR [rdi+0x8],rax
15. Překlad do strojového kódu s použitím instrukcí SIMD
Součet odpovídajících si prvků vektorů je realizován instrukcí ADDPS:
14: 0f 28 45 f0 movaps xmm0,XMMWORD PTR [rbp-0x10] 18: 0f 58 45 e0 addps xmm0,XMMWORD PTR [rbp-0x20] 1c: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 20: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Rozdíl odpovídajících si prvků vektorů je realizován instrukcí SUBPS:
3a: 0f 28 45 f0 movaps xmm0,XMMWORD PTR [rbp-0x10] 3e: 0f 5c 45 e0 subps xmm0,XMMWORD PTR [rbp-0x20] 42: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 46: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Součin odpovídajících si prvků vektorů je realizován instrukcí MULPS:
60: 0f 28 45 f0 movaps xmm0,XMMWORD PTR [rbp-0x10] 64: 0f 59 45 e0 mulps xmm0,XMMWORD PTR [rbp-0x20] 68: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 6c: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Podíl odpovídajících si prvků vektorů je realizován instrukcí DIVPS:
86: 0f 28 45 f0 movaps xmm0,XMMWORD PTR [rbp-0x10] 8a: 0f 5e 45 e0 divps xmm0,XMMWORD PTR [rbp-0x20] 8e: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 92: 0f 29 00 movaps XMMWORD PTR [rax],xmm0
Po optimalizaci (-O3) se samotné výpočty zkrátí – nemusí se provádět zbytečné přesuny dat:
Součet odpovídajících si prvků vektorů:
4: 0f 58 c1 addps xmm0,xmm1 7: 0f 29 07 movaps XMMWORD PTR [rdi],xmm0
Rozdíl odpovídajících si prvků vektorů:
14: 0f 5c c1 subps xmm0,xmm1 17: 0f 29 07 movaps XMMWORD PTR [rdi],xmm0
Součin odpovídajících si prvků vektorů:
24: 0f 59 c1 mulps xmm0,xmm1 27: 0f 29 07 movaps XMMWORD PTR [rdi],xmm0
Podíl odpovídajících si prvků vektorů:
34: 0f 5e c1 divps xmm0,xmm1 37: 0f 29 07 movaps XMMWORD PTR [rdi],xmm0
16. Zásadní omezení rozšíření (sic) GCC
V instrukčním souboru všech moderních mikroprocesorů x86–64 nalezneme celou řadu potenciálně velmi užitečných instrukcí, například instrukci PMADDWS, která provádí paralelní součin čtveřice šestnáctibitových hodnot s 32 bitovým mezivýsledkem, s následným součtem prvního + druhého a třetího + čtvrtého mezivýsledku. Užitečné jsou i původní MMX instrukce využitelné při práci s rastrovými obrázky nebo zvukovými vzorky (PACKUSWB, PACKSS, PUNPCKH, PUNPCKL). Podobně nalezneme v SSE instrukce SHUFPS, UNPCKHPS, UNPCKLPS, popř. různé výpočty:
- SQRTSS, SQRTPS: druhá odmocnina
- RSQRTSS, RSQRTPS: převrácená hodnota z druhé odmocniny
- MAXSS, MAXPS: výpočet maxima
- MINSS, MINPS: výpočet minima
Pro přímé využití těchto (a mnoha dalších podobných) instrukcí se musíme uchýlit k použití intrinsic, což je téma na samostatný článek.
17. SIMD a vektorové operace na architekturách AArch64 a RISC-V
Prakticky každá významnější společnost (v případě mikroprocesorů řady PowerPC pak dokonce aliance 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éž předchozí č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 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 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 | Scalable Vector Extension (SVE) | ARMv8.2-A a novější |
V navazujícím článku se budeme zabývat těmi SIMD/vektorovými instrukcemi, které jsou implementovány na architekturách AArch64 (NEON) a RISC-V.
18. Příloha – soubor Makefile použitý v dnešním článku
Následující soubor Makefile byl použit pro překlad zdrojových kódů všech výše uvedených demonstračních příkladů do objektového kódu jeho s následným disassemblingem do assembleru (resp. přesněji řečeno do assembleru zkombinovaného s hexadecimálním výpisem obsahu souboru s objektovým kódem):
CC=gcc OBJDUMP=objdump all: simd04_1.lst simd04_2.lst \ simd04B_1.lst simd04B_2.lst \ simd07_1.lst simd07_2.lst \ simd08_1.lst simd08_2.lst \ simd12_1.lst simd12_2.lst \ simd13_1.lst simd13_2.lst simd13_3.lst simd13_4.lst \ simd14_1.lst simd14_2.lst simd14_3.lst simd14_4.lst \ simd15_1.lst simd15_2.lst simd15_3.lst simd15_4.lst \ simd16_1.lst simd16_2.lst simd16_3.lst simd16_4.lst \ simd17_1.lst simd17_2.lst simd17_3.lst simd17_4.lst \ simd18_1.lst simd18_2.lst simd18_3.lst simd18_4.lst clean: rm *.lst rm *.o %.lst: %.o objdump -d -M intel -S $< > $@ simd04_1.o: simd04.c gcc -c -O0 -mno-sse -g -o $@ $< simd04_2.o: simd04.c gcc -c -O0 -g -o $@ $< simd04B_1.o: simd04B.c gcc -c -O0 -mno-sse -g -o $@ $< simd04B_2.o: simd04B.c gcc -c -O0 -g -o $@ $< simd07_1.o: simd07.c gcc -c -mno-sse -g -o $@ $< simd07_2.o: simd07.c gcc -c -g -o $@ $< simd08_1.o: simd08.c gcc -c -mno-sse -g -o $@ $< simd08_2.o: simd08.c gcc -c -g -o $@ $< simd12_1.o: simd12.c gcc -c -O0 -mno-sse -g -o $@ $< simd12_2.o: simd12.c gcc -c -O0 -g -o $@ $< simd13_1.o: simd13.c gcc -c -O0 -mno-sse -g -o $@ $< simd13_2.o: simd13.c gcc -c -O0 -g -o $@ $< simd13_3.o: simd13.c gcc -c -O3 -mno-sse -g -o $@ $< simd13_4.o: simd13.c gcc -c -O3 -g -o $@ $< simd14_1.o: simd14.c gcc -c -O0 -mno-sse -g -o $@ $< simd14_2.o: simd14.c gcc -c -O0 -g -o $@ $< simd14_3.o: simd14.c gcc -c -O3 -mno-sse -g -o $@ $< simd14_4.o: simd14.c gcc -c -O3 -g -o $@ $< simd15_1.o: simd15.c gcc -c -O0 -mno-sse -g -o $@ $< simd15_2.o: simd15.c gcc -c -O0 -g -o $@ $< simd15_3.o: simd15.c gcc -c -O3 -mno-sse -g -o $@ $< simd15_4.o: simd15.c gcc -c -O3 -g -o $@ $< simd16_1.o: simd16.c gcc -c -O0 -mno-sse -g -o $@ $< simd16_2.o: simd16.c gcc -c -O0 -g -o $@ $< simd16_3.o: simd16.c gcc -c -O3 -mno-sse -g -o $@ $< simd16_4.o: simd16.c gcc -c -O3 -g -o $@ $< simd17_1.o: simd17.c gcc -c -O0 -mno-sse -g -o $@ $< simd17_2.o: simd17.c gcc -c -O0 -g -o $@ $< simd17_3.o: simd17.c gcc -c -O3 -mno-sse -g -o $@ $< simd17_4.o: simd17.c gcc -c -O3 -g -o $@ $< simd18_1.o: simd18.c gcc -c -O0 -mno-sse -g -o $@ $< simd18_2.o: simd18.c gcc -c -O0 -g -o $@ $< simd18_3.o: simd18.c gcc -c -O3 -mno-sse -g -o $@ $< simd18_4.o: simd18.c gcc -c -O3 -g -o $@ $<
19. Repositář s demonstračními příklady
Demonstrační příklady napsané v jazyku C, které jsou určené pro překlad pomocí překladače GCC C, byly uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/presentations. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již velmi rozsáhlý) repositář:
Soubory vzniklé překladem z jazyka C do assembleru:
# | Příklad | Stručný popis | Adresa |
---|---|---|---|
1 | simd04_1.lst | překlad zdrojového kódu simd04_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04_1.lst |
2 | simd04_2.lst | překlad zdrojového kódu simd04_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04_2.lst |
3 | simd04B1.lst | překlad zdrojového kódu simd04B1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04B1.lst |
4 | simd04B2.lst | překlad zdrojového kódu simd04B2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd04B2.lst |
5 | simd07_1.lst | překlad zdrojového kódu simd07_1.c s přepínači -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd07_1.lst |
6 | simd07_2.lst | překlad zdrojového kódu simd07_2.c s přepínači -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd07_2.lst |
7 | simd08_1.lst | překlad zdrojového kódu simd08_1.c s přepínači -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd08_1.lst |
8 | simd08_2.lst | překlad zdrojového kódu simd08_2.c s přepínači -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd08_2.lst |
9 | simd12_1.lst | překlad zdrojového kódu simd12_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd12_1.lst |
10 | simd12_2.lst | překlad zdrojového kódu simd12_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd12_2.lst |
11 | simd13_1.lst | překlad zdrojového kódu simd13_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_1.lst |
12 | simd13_2.lst | překlad zdrojového kódu simd13_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_2.lst |
13 | simd13_3.lst | překlad zdrojového kódu simd13_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_3.lst |
14 | simd13_4.lst | překlad zdrojového kódu simd13_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd13_4.lst |
15 | simd14_1.lst | překlad zdrojového kódu simd14_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_1.lst |
16 | simd14_2.lst | překlad zdrojového kódu simd14_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_2.lst |
17 | simd14_3.lst | překlad zdrojového kódu simd14_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_3.lst |
18 | simd14_4.lst | překlad zdrojového kódu simd14_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd14_4.lst |
19 | simd15_1.lst | překlad zdrojového kódu simd15_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_1.lst |
20 | simd15_2.lst | překlad zdrojového kódu simd15_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_2.lst |
21 | simd15_3.lst | překlad zdrojového kódu simd15_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_3.lst |
22 | simd15_4.lst | překlad zdrojového kódu simd15_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd15_4.lst |
23 | simd16_1.lst | překlad zdrojového kódu simd16_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_1.lst |
24 | simd16_2.lst | překlad zdrojového kódu simd16_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_2.lst |
25 | simd16_3.lst | překlad zdrojového kódu simd16_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_3.lst |
26 | simd16_4.lst | překlad zdrojového kódu simd16_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd16_4.lst |
27 | simd17_1.lst | překlad zdrojového kódu simd17_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_1.lst |
28 | simd17_2.lst | překlad zdrojového kódu simd17_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_2.lst |
29 | simd17_3.lst | překlad zdrojového kódu simd17_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_3.lst |
30 | simd17_4.lst | překlad zdrojového kódu simd17_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd17_4.lst |
31 | simd18_1.lst | překlad zdrojového kódu simd18_1.c s přepínači -O0 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_1.lst |
32 | simd18_2.lst | překlad zdrojového kódu simd18_2.c s přepínači -O0 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_2.lst |
33 | simd18_3.lst | překlad zdrojového kódu simd18_3.c s přepínači -O3 -mno-sse -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_3.lst |
34 | simd18_4.lst | překlad zdrojového kódu simd18_4.c s přepínači -O3 -g | https://github.com/tisnik/presentations/blob/master/SIMD/simd18_4.lst |
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