Instrukční sady SIMD a automatické vektorizace prováděné překladačem GCC (2)

20. 3. 2025
Doba čtení: 30 minut

Sdílet

Autor: Depositphotos
Na úvodní článek o automatických vektorizacích prováděných překladači dnes navážeme. Opět se zaměříme na autovektorizace prováděné překladačem GCC, dnes si ale otestujeme poněkud komplikovanější funkce, které se mají optimalizovat.

Obsah

1. Instrukční sady SIMD a automatické vektorizace prováděné překladačem GCC (2)

2. První příklad: součet všech prvků v krátkém jednorozměrném poli se čtyřmi prvky

3. Výsledek překladu se zákazem i povolením automatické vektorizace

4. Automatická vektorizace po specifikaci přepínače -ffast-math

5. Vysvětlení postupu výpočtu

6. Druhý příklad: součet všech prvků v krátkém jednorozměrném poli s osmi prvky

7. Výsledné kódy vygenerované překladačem: „skalární“ i „vektorová“ varianta

8. Třetí příklad: součet pole se 100 prvky

9. Výsledné kódy vygenerované překladačem: „skalární“ i „vektorová“ varianta

10. Čtvrtý příklad: vyhledání největšího prvku v poli

11. Nevektorizovaná a vektorizovaná podoba výsledného kódu

12. Vliv velikosti pole na výsledný vygenerovaný kód

13. Jeden z nejdůležitějších algoritmů současnosti

14. Pátý příklad: skalární součin dvou krátkých polí se čtyřmi prvky

15. Nevektorizovaná a vektorizovaná podoba výsledného kódu

16. Další možné optimalizace výpočtů skalárního součinu

17. Vysvětlení postupu výpočtu

18. Repositář s demonstračními příklady

19. Seznam všech předchozích částí tohoto seriálu a článků o SIMD instrukcích

20. Odkazy na Internetu

1. Instrukční sady SIMD a automatické vektorizace prováděné překladačem GCC (2)

V úvodním článku o technologii automatické vektorizace prováděné při překladu zdrojových kódů do strojového kódu (nebo do assembleru) jsme si ukázali základní techniky podporované překladačem GCC v případě, že se nějaká operace provádí se všemi prvky pole (resp. v terminologii SSE vektoru). Jednalo se například o operace vynulování všech prvků pole, přičtení hodnoty ke všem prvkům pole nebo součet odpovídajících si prvků dvou polí.

Ovšem technologie automatické vektorizace dokáže v některých případech korektně zpracovat i smyčky, ve kterých se provádí „redukce“ – součet všech prvků v poli, nalezení nejmenšího nebo naopak největšího prvku v poli, ale i výpočet skalárního součinu atd. A právě na způsob překladu takových zdrojových kódů se zaměříme dnes. Pro jednoduchost se stále budeme omezovat na instrukce SSE.

2. První příklad: součet všech prvků v krátkém jednorozměrném poli se čtyřmi prvky

V dnešním prvním demonstračním příkladu si otestujeme, jakým způsobem je možné vektorizovat zdrojový kód, ve kterém se sice zpracovávají všechny prvky nějakého pole (resp. vektoru), ovšem výsledkem tohoto zpracování bude jediná skalární hodnota vzniklá zkombinováním hodnot všech prvků. Nejjednodušší takový příklad počítá sumu všech prvků pole a realizovat je ho možné velmi snadno. Začneme tím nejmenším možným případem, který je ještě možné vektorizovat – součtem všech (čtyř) prvků čtyřprvkového pole:

float array_sum_4(float *a) {
    #define SIZE 4
    int i;
    float result = 0.0;
    for (i=0; i<SIZE; i++) {
        result += a[i];
    }
    return result;
}

Překladač tedy musí umět rozeznat, že se provádí nějaká forma algoritmu typu reduce (někdy se setkáme s označením reduction operator, které je přesnější).

3. Výsledek překladu se zákazem i povolením automatické vektorizace

Podívejme se nyní, jak bude vypadat výsledek překladu céčkovského kódu do assembleru (resp. později do strojového kódu). První kód byl překladačem vytvořen ve chvíli, kdy byly zakázány veškeré optimalizace (neuvedli jsme totiž přepínač -O2). Povšimněte si, že je realizována počítaná programová smyčka, ve které se volají „skalární“ instrukce MOVSS a ADDSS. Dokonce ani nedošlo k pokusu o rozbalení této smyčky (což je dobře, když jsme optimalizace zakázali):

array_sum:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-24], rdi
        pxor    xmm0, xmm0
        movss   DWORD PTR [rbp-8], xmm0
        mov     DWORD PTR [rbp-4], 0
        jmp     .L2
.L3:
        mov     eax, DWORD PTR [rbp-4]
        cdqe
        lea     rdx, [0+rax*4]
        mov     rax, QWORD PTR [rbp-24]
        add     rax, rdx
        movss   xmm0, DWORD PTR [rax]
        movss   xmm1, DWORD PTR [rbp-8]
        addss   xmm0, xmm1
        movss   DWORD PTR [rbp-8], xmm0
        add     DWORD PTR [rbp-4], 1
.L2:
        cmp     DWORD PTR [rbp-4], 3
        jle     .L3
        movss   xmm0, DWORD PTR [rbp-8]
        pop     rbp
        ret

Po povolení optimalizací, ovšem bez povolení automatické vektorizace, je výsledek mnohem lepší. Namísto programové smyčky se pouze provede součet všech prvků pole. Zajímavé je, že se stále začíná s nulovou hodnotou uloženou do registru XMM0, i když by teoreticky bylo možné do tohoto registru uložit přímo první prvek pole (vyžadujeme ovšem chování podle normy IEEEE 754):

array_sum:
        pxor    xmm0, xmm0
        addss   xmm0, DWORD PTR [rdi]
        addss   xmm0, DWORD PTR [rdi+4]
        addss   xmm0, DWORD PTR [rdi+8]
        addss   xmm0, DWORD PTR [rdi+12]
        ret

Zajímavé je, že i když automatickou vektorizaci povolíme, výsledný kód se nijak nezmění a bude stále používat „skalární“ instrukce:

array_sum:
        pxor    xmm0, xmm0
        addss   xmm0, DWORD PTR [rdi]
        addss   xmm0, DWORD PTR [rdi+4]
        addss   xmm0, DWORD PTR [rdi+8]
        addss   xmm0, DWORD PTR [rdi+12]
        ret

4. Automatická vektorizace po specifikaci přepínače -ffast-math

Jak ovšem dosáhneme toho, aby se náš zdrojový kód přeložil do „vektorizované“ podoby? V tomto případě budeme muset použít přepínač -ffast-math, který překladači umožní vložit do výsledného kódu instrukce, jejichž chování nebude přesně odpovídat normě IEEE754 (pořadí operací, práce s denormalizovanými hodnotami atd.), takže může nastat situace, kdy ten stejný výpočet (na úrovni původního zdrojového kódu) povede k nepatrně odlišným výsledkům. To nám ovšem mnohdy nevadí.

Výsledná vektorizovaná podoba vypadá takto:

array_sum:
        movups  xmm0, XMMWORD PTR [rdi]
        movaps  xmm1, xmm0
        movhlps xmm1, xmm0
        addps   xmm1, xmm0
        movaps  xmm0, xmm1
        shufps  xmm0, xmm1, 85
        addps   xmm0, xmm1
        ret

5. Vysvětlení postupu výpočtu

Co vlastně výše uvedená sekvence instrukcí znamená? Zkusme si ji popsat postupně, a to na vstupním vektoru obsahujícím hodnoty [1000, 100, 10, 1] (schválně nepoužívám hodnoty 1, 2, 3, 4, protože jejich mezisoučty jsou nejasné). Nejdříve se čtveřice hodnot [1000, 100, 10, 1] přesune do registru XMM0 a posléze i do registru XMM1. Obsah obou registrů tedy bude vypadat následovně:

XMM0: 00001000 00000100 00000010 00000001
XMM1: 00001000 00000100 00000010 00000001

Posléze se instrukcí MOVHLPS přenesou nejvyšší dva prvky z registru XMM0 do spodních dvou prvků registru XMM1, takže obsah registrů bude vypadat takto:

XMM0: 00001000 00000100 00000010 00000001
XMM1: 00001000 00000100 00001000 00000100

Po vektorovém součtu instrukcí ADDPS bude v registru XMM1 uložen vektor s prvky [2000, 200, 1010, 101], tj. spodní dva prvky již budou obsahovat dva mezisoučty (označme 101 jako první mezisoučet a 1010 jako mezisoučet druhý):

XMM0: 00001000 00000100 00000010 00000001
XMM1: 00002000 00000200 00001010 00000101

Instrukcemi MOVAPS XMM0, XMM1 a SHUFPS xmm0, xmm1, 01010101 se do registru XMM0 uloží prvky s indexem jedna z registrů XMM0 a XMM1, tj. konkrétně druhý mezisoučet 1010. V registru XMM1 je v horních prvcích uloženo smetí a v nejnižším prvku pak stále máme první mezisoučet 101:

XMM0: 00001010 00001010 00001010 00001010
XMM1: 00002000 00000200 00001010 00000101

Poslední součet instrukcí ADDPS nám již dá v nejnižším prvku vektoru XMM0 kýžený výsledek:

XMM0: 00003010 00001210 00002020 00001111
Poznámka: zde by postačovalo použít instrukci ADDSS a nikoli ADDPS.

Vše si můžeme otestovat tímto kódem (přeložitelným v Linuxu):

[bits 32]
 
%include "linux_macros.asm"
 
;-----------------------------------------------------------------------------
section .data
 
hex_message:
         times 8 db '?'
         db ' '
         hex_message_length equ $ - hex_message
 
align 16
sse_val_1 dd 0x1, 0x10, 0x100, 0x1000
 
;-----------------------------------------------------------------------------
section .bss
sse_tmp resb 16
 
 
;-----------------------------------------------------------------------------
section .text
        global _start                ; tento symbol ma byt dostupny i linkeru
 
_start:
        mov ebx, sse_val_1
        movaps xmm0, [ebx]           ; nacteni prvni hodnoty do registru XMM0
 
        movaps  xmm1, xmm0           ; xmm1 := xmm0
        print_sse_reg_as_hex xmm0    ; tisk hodnoty registru XMM0
        print_sse_reg_as_hex xmm1    ; tisk hodnoty registru XMM1
 
        movhlps xmm1, xmm0           ; přesun nejvyšších dvou prvků vektoru
        print_sse_reg_as_hex xmm0    ; tisk hodnoty registru XMM0
        print_sse_reg_as_hex xmm1    ; tisk hodnoty registru XMM1
 
        addps   xmm1, xmm0           ; vektorový součet
        print_sse_reg_as_hex xmm0    ; tisk hodnoty registru XMM0
        print_sse_reg_as_hex xmm1    ; tisk hodnoty registru XMM1
 
        movaps  xmm0, xmm1
        shufps  xmm0, xmm1, 85       ; proložení prvků vektorů
        print_sse_reg_as_hex xmm0    ; tisk hodnoty registru XMM0
        print_sse_reg_as_hex xmm1    ; tisk hodnoty registru XMM1
 
        addps   xmm0, xmm1           ; vektorový součet
        print_sse_reg_as_hex xmm0    ; tisk hodnoty registru XMM0
 
        exit                         ; ukonceni procesu
 
 
%include "hex2string.asm"
Poznámka: pro jednoduchost jsem zde použil hexadecimální hodnoty, které na způsobu výpočtu nic nemění.

6. Druhý příklad: součet všech prvků v krátkém jednorozměrném poli s osmi prvky

Zkusme si nyní celé pole, jehož prvky se sčítají, zvětšit ze čtyř prvků na prvků osm. Tím donutíme překladač, aby součet provedl minimálně dvakrát, protože do XMM registru je možné uložit maximálně čtyři prvky typu single/float. Příklad je upraven pouze nepatrně – pozměnili jsme konstantu SIZE:

float array_sum_8(float *a) {
    #define SIZE 8
    int i;
    float result = 0.0;
    for (i=0; i<SIZE; i++) {
        result += a[i];
    }
    return result;
}

7. Výsledné kódy vygenerované překladačem: „skalární“ i „vektorová“ varianta

Opět se podívejme na výsledné sekvence instrukcí vygenerovaných překladačem. V případě, že jsou optimalizace zakázány, je výsledkem (podle očekávání) stále stejná neoptimalizovaná počítaná programová smyčka:

array_sum:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-24], rdi
        pxor    xmm0, xmm0
        movss   DWORD PTR [rbp-8], xmm0
        mov     DWORD PTR [rbp-4], 0
        jmp     .L2
.L3:
        mov     eax, DWORD PTR [rbp-4]
        cdqe
        lea     rdx, [0+rax*4]
        mov     rax, QWORD PTR [rbp-24]
        add     rax, rdx
        movss   xmm0, DWORD PTR [rax]
        movss   xmm1, DWORD PTR [rbp-8]
        addss   xmm0, xmm1
        movss   DWORD PTR [rbp-8], xmm0
        add     DWORD PTR [rbp-4], 1
.L2:
        cmp     DWORD PTR [rbp-4], 7
        jle     .L3
        movss   xmm0, DWORD PTR [rbp-8]
        pop     rbp
        ret

Při zapnutí optimalizací, ovšem bez použití autovektorizace, nyní získáme počítanou smyčku; sice krátkou a efektivní, ale jedná se o smyčku:

array_sum:
        lea     rax, [rdi+32]
        pxor    xmm0, xmm0
.L2:
        addss   xmm0, DWORD PTR [rdi]
        add     rdi, 8
        addss   xmm0, DWORD PTR [rdi-4]
        cmp     rdi, rax
        jne     .L2
        ret

Ovšem při povolení automatické vektorizace a současně i výpočtů neodpovídajících přesně normě IEEE 754 („rychlá matematika“) je výsledek mnohem lepší. Dostaneme vlastně podobný kód, jako u pole se čtyřmi prvky, nyní ovšem hned na začátku proběhne součet prvku číslo 0 s prvkem číslo 4, dále prvku číslo 1 s prvkem číslo 5 atd. – tedy první mezisoučet, jehož výsledkem jsou čtyři částečné součty. A následnou sekvenci instrukcí již známe:

array_sum:
        movups  xmm1, XMMWORD PTR [rdi+16]
        movups  xmm0, XMMWORD PTR [rdi]
        addps   xmm0, xmm1
        movaps  xmm1, xmm0
        movhlps xmm1, xmm0
        addps   xmm1, xmm0
        movaps  xmm0, xmm1
        shufps  xmm0, xmm1, 85
        addps   xmm0, xmm1
        ret

8. Třetí příklad: součet pole se 100 prvky

Naposledy se ještě podívejme na problematiku součtu prvků polí, tentokrát pro rozsáhlé pole se sto prvky. Takový počet by již překladač měl donutit k vygenerování programové smyčky (i když si dovedu představit situaci, kdy bude vhodnější celý kód rozbalit). Naivní a nijak neoptimalizovaná funkce pro součet prvků pole o délce 100 bude vypadat takto:

float array_sum_100(float *a) {
    #define SIZE 100
    int i;
    float result = 0.0;
    for (i=0; i<SIZE; i++) {
        result += a[i];
    }
    return result;
}

9. Výsledné kódy vygenerované překladačem: „skalární“ i „vektorová“ varianta

Skalární, ovšem optimalizovaná varianta výpočtu, bude vypadat poněkud zajímavě, protože z ní můžeme vyčíst, že se v jedné iteraci smyčky provádí přičtení hodnoty dvou prvků. Smyčka je tedy částečně rozbalena. Taktéž je užitečné, že se hodnota registru RDI (adresa prvku) zvyšuje před provedením součtu, což sice poněkud komplikuje adresování, ale po součtu již bude možné novou hodnotu porovnat s mezní hodnotou. Tím se vynechá (překryje) čekání na výsledek přičtení osmičky k RDI:

array_sum100:
        lea     rax, [rdi+400]
        pxor    xmm0, xmm0
.L2:
        addss   xmm0, DWORD PTR [rdi]
        add     rdi, 8
        addss   xmm0, DWORD PTR [rdi-4]
        cmp     rdi, rax
        jne     .L2
        ret

Vektorová varianta je vlastně variací na známé téma, tentokrát je ovšem akumulátor (výsledek celého součtu) uložen v registru XMM0 a vektorové součty se provádí s registry XMM1 a XMM2:

array_sum100:
        lea     rax, [rdi+400]
        pxor    xmm0, xmm0
.L2:
        movups  xmm2, XMMWORD PTR [rdi]
        add     rdi, 16
        addps   xmm0, xmm2
        cmp     rax, rdi
        jne     .L2
        movaps  xmm1, xmm0
        movhlps xmm1, xmm0
        addps   xmm1, xmm0
        movaps  xmm0, xmm1
        shufps  xmm0, xmm1, 85
        addps   xmm0, xmm1
        ret

10. Čtvrtý příklad: vyhledání největšího prvku v poli

Další ukázkou operace typu reduce je vyhledání největšího prvku v poli. To vlastně znamená opakované testování hodnoty prvků vůči předchozí maximální hodnotě, takže bude zajímavé zjistit, zda i tento kód je možné vektorizovat, resp. jestli to dokáže překladač GCC. Začneme opět tím nejjednodušším možným případem: polem se čtyřmi prvky:

#include <float.h>
 
float find_max_4(float *a) {
    #define SIZE 4
    int i;
    float max = FLT_MIN_EXP;
    for (i=0; i<SIZE; i++) {
        if (a[i] > max) {
            max = a[i];
        }
    }
    return max;
}

11. Nevektorizovaná a vektorizovaná podoba výsledného kódu

Podobně jako u předchozích demonstračních příkladů se i nyní podíváme na výsledný kód vygenerovaný překladačem GCC. Nejprve je ukázána skalární varianta založená na programové smyčce, ve které se volá „skalární“ instrukce MAXSS, s níž jsme se již setkali.

find_max_4:
        movss   xmm0, DWORD PTR .LC0[rip]
        lea     rax, [rdi+16]
.L2:
        movss   xmm1, DWORD PTR [rdi]
        add     rdi, 4
        maxss   xmm1, xmm0
        movaps  xmm0, xmm1
        cmp     rdi, rax
        jne     .L2
        ret
.LC0:
        .long   -1023803392

Optimalizovaná skalární varianta explicitně zavolá instrukci MAXSS postupně pro všechny prvky pole. Jedná se tedy o rozbalenou variantu programové smyčky, což je pochopitelně rychlejší řešení:

find_max_4:
        movss   xmm0, DWORD PTR [rdi]
        movss   xmm1, DWORD PTR [rdi+8]
        maxss   xmm0, DWORD PTR [rdi+4]
        maxss   xmm1, DWORD PTR .LC0[rip]
        maxss   xmm0, DWORD PTR [rdi+12]
        maxss   xmm0, xmm1
        ret
.LC0:
        .long   -1023803392

A konečně se podívejme na vektorizovanou variantu. Povšimněte si, jak se tato varianta podobá výpočtu součtu prvků čtyřprvkového pole – ovšem pochopitelně se nyní namísto instrukce ADDPS volá instrukce MAXPS:

find_max_4:
        movups  xmm0, XMMWORD PTR [rdi]
        movaps  xmm1, xmm0
        movhlps xmm1, xmm0
        maxps   xmm1, xmm0
        movaps  xmm0, xmm1
        shufps  xmm0, xmm1, 85
        maxps   xmm0, xmm1
        maxss   xmm0, DWORD PTR .LC0[rip]
        ret
.LC0:
        .long   -1023803392

Pro zajímavost si oba vektorizované výpočty porovnejme po jednotlivých instrukcích, aby ještě více vynikla jejich podobnost:

součet všech prvků pole
movups  xmm0, XMMWORD PTR [rdi]  movups  xmm0, XMMWORD PTR [rdi]
movaps  xmm1, xmm0               movaps  xmm1, xmm0
movhlps xmm1, xmm0               movhlps xmm1, xmm0
addps   xmm1, xmm0               maxps   xmm1, xmm0
movaps  xmm0, xmm1               movaps  xmm0, xmm1
shufps  xmm0, xmm1, 85           shufps  xmm0, xmm1, 85
addps   xmm0, xmm1               maxps   xmm0, xmm1
ret                              maxss   xmm0, DWORD PTR .LC0[rip]
                                 ret

12. Vliv velikosti pole na výsledný vygenerovaný kód

Samozřejmě si otestujeme, jaký vliv bude mít velikost pole (vektoru) na vygenerovaný strojový kód nebo assembler. Pro pole o počtu prvků osm dostaneme následující výsledky.

Skalární varianta, ve které se už používá programová smyčka a v každé iteraci se zpracují dva prvky pole:

find_max_8:
        movss   xmm0, DWORD PTR .LC0[rip]
        lea     rax, [rdi+32]
.L2:
        maxss   xmm0, DWORD PTR [rdi]
        add     rdi, 8
        maxss   xmm0, DWORD PTR [rdi-4]
        cmp     rdi, rax
        jne     .L2
        ret
.LC0:
        .long   -1023803392

Vektorová varianta, ve které překladač smyčku rozbalil, ovšem stále potřebuje čtyři instrukce MAXPS:

find_max_8:
        movss   xmm0, DWORD PTR .LC1[rip]
        movups  xmm1, XMMWORD PTR [rdi]
        shufps  xmm0, xmm0, 0
        maxps   xmm1, xmm0
        movups  xmm0, XMMWORD PTR [rdi+16]
        maxps   xmm0, xmm1
        movaps  xmm1, xmm0
        movhlps xmm1, xmm0
        maxps   xmm1, xmm0
        movaps  xmm0, xmm1
        shufps  xmm0, xmm1, 85
        maxps   xmm0, xmm1
        ret
.LC1:
        .long   -1023803392

Následuje výsledek překladu s automatickou vektorizací pro pole, které obsahuje 100 prvků. To již pochopitelně vede k vygenerování programové smyčky namísto jejího rozbalení. Nicméně stále se v jedné iteraci nalezne maximální hodnota čtyř nových prvků a porovná se s průběžně „akumulovanou“ maximální hodnotou:

find_max_100:
        movss   xmm0, DWORD PTR .LC1[rip]
        lea     rax, [rdi+400]
        shufps  xmm0, xmm0, 0
.L2:
        movups  xmm2, XMMWORD PTR [rdi]
        add     rdi, 16
        maxps   xmm0, xmm2
        cmp     rax, rdi
        jne     .L2
        movaps  xmm1, xmm0
        movhlps xmm1, xmm0
        maxps   xmm1, xmm0
        movaps  xmm0, xmm1
        shufps  xmm0, xmm1, 85
        maxps   xmm0, xmm1
        ret
.LC1:
        .long   -1023803392

13. Jeden z nejdůležitějších algoritmů současnosti

S rozvojem neuronových sítí a umělé inteligence (ale nejenom zde) se začal masivně využívat známý algoritmus pro výpočet skalárního součinu (dot product, scalar product). Tento algoritmus se používá v oblasti velkých jazykových modelů (LLM) pro zjišťování podobnosti dlouhých vektorů s numerickými hodnotami (vector similarity). Kromě klasického skalárního součinu se v této oblasti používá i tzv. cosinus similarity, což je varianta skalárního součinu, v níž nezáleží na délce vektorů, ale pouze na jejich vzájemné orientaci (výpočet je tedy doplněn o normalizaci vektorů). A toto porovnávání vektorů se v LLM provádí neustále a většinou je optimalizováno a výpočty běží na GPU.

To však není zdaleka vše. Pokud se zaměříme na oblast klasických neuronových sítí (NN – neural networks), zjistíme, že se tyto sítě skládají z takzvaných perceptronů, což je vlastně značně zjednodušený model neuronu. A na vstup perceptronu se přivádí nějaké množství numerických vstupů. Každý z těchto vstupů je váhován, tj. vynásoben určitou konstantou a výsledky tohoto váhování jsou nakonec sečteny. Když se ovšem nad touto operací zamyslíme, zjistíme, že se vlastně nejedná o nic jiného, než o aplikaci výpočtu skalárního součinu. První z vektorů, který do tohoto součinu vstupuje jako operand, jsou vstupy do neuronu, druhým vektorem je pak vektor vah, které si neuron zapamatoval. A samotný trénink neuronové sítě vlastně není nic jiného, než rekonfigurace těchto vah – vektorů:

torch-nn1-1

Obrázek 1: Idealizovaný model neuronu.

torch-nn1-2

Obrázek 2: Idealizovaný model neuronu s biasem.

Poznámka: neuronovými sítěmi tohoto typu jsme se podrobněji zabývali v seriálu o knihovně Scilik-learn.

14. Pátý příklad: skalární součin dvou krátkých polí se čtyřmi prvky

V dnešním pátém ukázkovém příkladu je realizován algoritmus pro výpočet skalárního součinu dvou čtyřprvkových vektorů. Funkce dot_product4 je přitom napsána zcela naivním způsobem, bez jakékoli snahy o její optimalizaci na úrovni céčkového kódu:

float dot_product_4(float *a, float *b) {
    #define SIZE 4
    int i;
    float result = 0.0;
    for (i=0; i<SIZE; i++) {
        result += a[i] * b[i];
    }
    return result;
}

15. Nevektorizovaná a vektorizovaná podoba výsledného kódu

Opět se, podobně jako u předchozích příkladů, podíváme na výsledek překladu výše uvedeného výpočtu do strojového kódu. Nejprve je uvedena nevektorizovaná varianta. Ta je v praxi dosti nezajímavá – moc pomalá, protože provádí čtyři součiny a čtyři součty:

dot_product_4:
        xor     eax, eax
        pxor    xmm1, xmm1
.L2:
        movss   xmm0, DWORD PTR [rdi+rax]
        mulss   xmm0, DWORD PTR [rsi+rax]
        add     rax, 4
        addss   xmm1, xmm0
        cmp     rax, 16
        jne     .L2
        movaps  xmm0, xmm1
        ret

Vektorizovaná varianta vypadá následovně. Jedná se o variantu kompatibilní s normou IEEE 754:

dot_product_4:
        movups  xmm0, XMMWORD PTR [rsi]
        movups  xmm1, XMMWORD PTR [rdi]
        pxor    xmm2, xmm2
        mulps   xmm1, xmm0
        movaps  xmm0, xmm1
        addss   xmm0, xmm2
        movaps  xmm2, xmm1
        shufps  xmm2, xmm1, 85
        addss   xmm0, xmm2
        movaps  xmm2, xmm1
        unpckhps        xmm2, xmm1
        shufps  xmm1, xmm1, 255
        addss   xmm0, xmm2
        addss   xmm0, xmm1
        ret

16. Další možné optimalizace výpočtů skalárního součinu

Nejkratší kód (bez smyčky) dostaneme v případě povolení autovektorizace a současně i povolením režimu „rychlé matematiky“ (fast-math), takže výsledky obecně nemusí přesně odpovídat normě IEEE 754. Tato varianta je založena na součinu prvků vektorů, součtu prvků vektorů a následně ještě jednoho součtu, který vrátí výslednou skalární hodnotu:

dot_product_4:
        movups  xmm1, XMMWORD PTR [rsi]
        movups  xmm0, XMMWORD PTR [rdi]
        mulps   xmm0, xmm1
        movaps  xmm1, xmm0
        movhlps xmm1, xmm0
        addps   xmm1, xmm0
        movaps  xmm0, xmm1
        shufps  xmm0, xmm1, 85
        addps   xmm0, xmm1
        ret

17. Vysvětlení postupu výpočtu

Sekvenci instrukcí z předchozí kapitoly si opět popíšeme podrobněji. Přitom provedeme výpočet skalárního součinu pro dvojici vektorů:

a = (1, 2, 3, 4)
b = (5, 6, 7, 1)

Ručně by se výpočet provedl takto:

a · b = a1·b1 + a2·b2 + a3·b3 + a4·b4 = 1·5 + 2·6 + 3·7 + 4·1 = 5 + 12 + 21 + 4 = 42
Poznámka: 42 je samozřejmě v tomto vesmíru očekávaný a správný výsledek.

Nejdříve jsou instrukcemi MOVUPS načteny oba vektory do pracovních registrů XMM1 a XMM0 (u každého registru je nejdříve vypsána jeho hexadecimální hodnota a poté odpovídající FP hodnoty):

XMM0:   40A00000 40C00000 40E00000 3F800000    5.0  6.0  7.0  1.0
XMM1:   3F800000 40000000 40400000 40800000    1.0  2.0  3.0  4.0

Instrukcí MULPS je proveden součin odpovídajících si prvků vektorů a výsledek je uložen do registru XMM0:

XMM0:   40A00000 41400000 41A80000 40800000    5.0  12.0  21.0  4.0
XMM1:   3F800000 40000000 40400000 40800000    1.0  2.0  3.0  4.0

XMM1 nyní neobsahuje žádné použitelné hodnoty, takže ho využijeme pro další výpočty. Nejdříve kopie XMM0 do XMM1:

XMM0:   40A00000 41400000 41A80000 40800000    5.0  12.0  21.0  4.0
XMM1:   40A00000 41400000 41A80000 40800000    5.0  12.0  21.0  4.0

A následně kopie pouze vyšších dvou prvků do prvků nižších v cílovém registru (provedeno instrukcí MOVHLPS. Nyní máme první dva mezivýsledky součinů v registru XMM1 jak na horních dvou, tak i na dolních dvou pozicích:

XMM0:   40A00000 41400000 41A80000 40800000    5.0  12.0  21.0  4.0
XMM1:   40A00000 41400000 40A00000 41400000    5.0  12.0  5.0  12.0

Oba vektory sečteme, takže získáme první dva mezisoučty:

XMM0:   40A00000 41400000 41A80000 40800000    5.0  12.0  21.0  4.0
XMM1:   41200000 41C00000 41D00000 41800000    10.0  24.0  26.0  16.0

Následuje známý trik spočívající v kombinovaném použití MOVAPS+SHUFPS s konstantou 85=0b01010101:

XMM0:   41D00000 41D00000 41D00000 41D00000    26.0  26.0  26.0  26.0
XMM1:   41200000 41C00000 41D00000 41800000    10.0  24.0  26.0  16.0

Nakonec nám postačuje součet (dokonce by mohlo jít o skalární součet, protože nás zajímá jen nejnižší prvek):

XMM0:   42100000 42480000 42500000 42280000    36.0  50.0  52.0  42.0
XMM1:   41200000 41C00000 41D00000 41800000    10.0  24.0  26.0  16.0

V registru XMM0 je v nejnižším prvku skutečně výsledek 42.0!

zabbix_tip

18. Repositář s demonstračními příklady

Demonstrační příklady naprogramované v jazyku, které jsou určené pro překlad s využitím assembleru gcc, byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Kromě zdrojových kódů příkladů jsou do repositáře přidány i výsledky překladu do assembleru v syntaxi kompatibilní s Intelem. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

# Příklad Stručný popis Adresa
1 array_clear_size8.c smazání obsahu pole s osmi prvky typu float https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size8.c
2 array_clear_size8_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size8_non_vect.asm
3 array_clear_size8_no_sse.asm výsledek překladu se zákazem SSE instrukcí https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size8_no_sse.asm
4 array_clear_size8_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size8_vect.asm
       
5 array_clear_size16.c smazání obsahu pole se šestnácti prvky typu float https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size16.c
6 array_clear_size16_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size16_non_vect.asm
7 array_clear_size16_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size16_vect.asm
       
8 array_clear_size18.c smazání obsahu pole se sedmnácti prvky typu float https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size18.c
9 array_clear_size18_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size18_non_vect.asm
10 array_clear_size18_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_clear_size18_vect.asm
       
11 add_delta_size16.c přičtení konstanty ke všem prvkům pole obsahujícího 16 hodnot typu float https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_delta_size16.c
12 add_delta_size16_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_delta_size16_non_vect.asm
13 add_delta_size16_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_delta_size16_vect.asm
       
14 add_delta_size17.c přičtení konstanty ke všem prvkům pole obsahujícího 17 hodnot typu float https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_delta_size17.c
15 add_delta_size17_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_delta_size17_non_vect.asm
16 add_delta_size17_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_delta_size17_vect.asm
       
17 add_delta_size24.c přičtení konstanty ke všem prvkům pole obsahujícího 24 hodnot typu float https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_delta_size24.c
18 add_delta_size24_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_delta_size24_non_vect.asm
19 add_delta_size24_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_delta_size24_vect.asm
       
20 array_sqrt.c výpočet druhé odmocniny všech prvků polí https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sqrt.c
21 array_sqrt_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sqrt_non_vect.asm
22 array_sqrt_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sqrt_vect.asm
23 array_sqrt_vect_fast_math.asm výsledek překladu s povolením vektorizace a nepřesných výpočtů https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sqrt_vect_fast_math.asm
       
24 dot_product4.c skalární součin vektorů, z nichž každý má délku čtyři prvky https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/dot_product4.c
25 dot_product4_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/dot_product4_non_vect.asm
26 dot_product4_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/dot_product4_vect.asm
       
27 dot_product8.c skalární součin vektorů, z nichž každý má délku osmi prvků https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/dot_product8.c
28 dot_product8_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/dot_product8_non_vect.asm
29 dot_product8_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/dot_product8_vect.asm
       
30 dot_product100.c skalární součin vektorů, z nichž každý má délku 100 prvků https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/dot_product100.c
31 dot_product100_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/dot_product100_non_vect.asm
32 dot_product100_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/dot_product100_vect.asm
       
33 add_arrays_size4.c součet prvků dvojice polí typu float, pole mají délku 4 prvků https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_size4.c
34 add_arrays_size4_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_size4_non_vect.asm
35 add_arrays_size4_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_size4_vect.asm
36 add_arrays_size16.c součet prvků dvojice polí typu float, pole mají délku 16 prvků https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_size16.c
37 add_arrays_size16_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_size16_non_vect.asm
38 add_arrays_size16_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_size16_vect.asm
39 add_arrays_size17.c součet prvků dvojice polí typu float, pole mají délku 17 prvků https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_size17.c
40 add_arrays_size17_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_size17_non_vect.asm
41 add_arrays_size17_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_size17_vect.asm
       
42 add_arrays_restrict_size4.c součet prvků polí se čtyřmi prvky, pole se nepřekrývají https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_restrict_size4.c
43 add_arrays_restrict_size4_non_vec­t.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_restrict_si­ze4_non_vect.asm
44 add_arrays_restrict_size4_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_restrict_si­ze4_vect.asm
45 add_arrays_restrict_size16.c součet prvků polí se šestnácti prvky, pole se nepřekrývají https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_restrict_size16.c
46 add_arrays_restrict_size16_non_vec­t.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_restrict_si­ze16_non_vect.asm
47 add_arrays_restrict_size16_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_restrict_si­ze16_vect.asm
48 add_arrays_restrict_size17.c součet prvků polí se sedmnácti prvky, pole se nepřekrývají https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_restrict_size17.c
49 add_arrays_restrict_size17_non_vec­t.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_restrict_si­ze17_non_vect.asm
50 add_arrays_restrict_size17_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/add_arrays_restrict_si­ze17_vect.asm
       
51 array_sum4.c součet všech prvků v poli se čtyřmi prvky https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sum4.c
52 array_sum4_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sum4_non_vect.asm
53 array_sum4_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sum4_vect.asm
54 array_sum8.c součet všech prvků v poli s osmi prvky https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sum8.c
55 array_sum8_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sum8_non_vect.asm
56 array_sum8_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sum8_vect.asm
57 array_sum100.c součet všech prvků polí se 100 prvky https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sum100.c
58 array_sum100_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sum100_non_vect.asm
59 array_sum100_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/array_sum100_vect.asm
       
60 find_max4.c vyhledání největšího prvku v poli se čtyřmi prvky https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/find_max4.c
61 find_max4_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/find_max4_non_vect.asm
62 find_max4_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/find_max4_vect.asm
63 find_max8.c vyhledání největšího prvku v poli s osmi prvky https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/find_max8.c
64 find_max8_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/find_max8_non_vect.asm
65 find_max8_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/find_max8_vect.asm
66 find_max100.c vyhledání největšího prvku v poli se 100 prvky https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/find_max100.c
67 find_max100_non_vect.asm výsledek překladu se zákazem vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/find_max100_non_vect.asm
68 find_max100_vect.asm výsledek překladu s povolením vektorizace https://github.com/tisnik/8bit-fame/blob/master/gcc-simd/find_max100_vect.asm

19. Seznam všech předchozích částí tohoto seriálu a článků o SIMD instrukcích

Podporou SIMD instrukcí na úrovni intrinsic jsme se už na Rootu zabývali, stejně jako samotnými SIMD instrukcemi na úrovni assembleru. Pro úplnost jsou v této příloze uvedeny odkazy na příslušné články:

  1. Užitečné rozšíření GCC: podpora SIMD (vektorových) instrukcí
    https://www.root.cz/clanky/uzitecne-rozsireni-gcc-podpora-simd-vektorovych-instrukci/
  2. Užitečné rozšíření GCC – podpora SIMD (vektorových) instrukcí: nedostatky technologie
    https://www.root.cz/clanky/uzitecne-rozsireni-gcc-podpora-simd-vektorovych-instrukci-nedostatky-technologie/
  3. Podpora SIMD (vektorových) instrukcí na RISCových procesorech
    https://www.root.cz/clanky/podpora-simd-vektorovych-instrukci-na-riscovych-procesorech/
  4. Podpora SIMD operací v GCC s využitím intrinsic pro nízkoúrovňové optimalizace
    https://www.root.cz/clanky/podpora-simd-operaci-v-gcc-s-vyuzitim-intrinsic-pro-nizkourovnove-optimalizace/
  5. Podpora SIMD operací v GCC s využitím intrinsic: technologie SSE
    https://www.root.cz/clanky/podpora-simd-operaci-v-gcc-s-vyuzitim-intrinsic-technologie-sse/
  6. Rozšíření instrukční sady „Advanced Vector Extensions“ na platformě x86–64
    https://www.root.cz/clanky/rozsireni-instrukcni-sady-advanced-vector-extensions-na-platforme-x86–64/
  7. Rozšíření instrukční sady F16C, FMA a AVX-512 na platformě x86–64
    https://www.root.cz/clanky/rozsireni-instrukcni-sady-f16c-fma-a-avx-512-na-platforme-x86–64/
  8. Rozšíření instrukční sady AVX-512 na platformě x86–64 (dokončení)
    https://www.root.cz/clanky/rozsireni-instrukcni-sady-avx-512-na-platforme-x86–64-dokonceni/
  9. SIMD instrukce na platformě 80×86: instrukční sada MMX
    https://www.root.cz/clanky/simd-instrukce-na-platforme-80×86-instrukcni-sada-mmx/
  10. SIMD instrukce na 80×86: dokončení popisu MMX, instrukce 3DNow!
    https://www.root.cz/clanky/simd-instrukce-na-80–86-dokonceni-popisu-mmx-instrukce-3dnow/
  11. SIMD instrukce v rozšíření SSE
    https://www.root.cz/clanky/simd-instrukce-v-rozsireni-sse/
  12. SIMD instrukce v rozšíření SSE (2. část)
    https://www.root.cz/clanky/simd-instrukce-v-rozsireni-sse-2-cast/
  13. Pokročilejší SSE operace: přeskupení, promíchání a rozbalování prvků vektorů
    https://www.root.cz/clanky/po­krocilejsi-sse-operace-preskupeni-promichani-a-rozbalovani-prvku-vektoru/
  14. Od instrukční sady SSE k sadě SSE2
    https://www.root.cz/clanky/od-instrukcni-sady-sse-k-sade-sse2/
  15. Instrukční sady SIMD a automatické vektorizace prováděné překladačem GCC
    https://www.root.cz/clanky/instrukcni-sady-simd-a-automaticke-vektorizace-provadene-prekladacem-gcc/

20. Odkazy na Internetu

  1. Auto-vectorization in GCC
    https://gcc.gnu.org/projects/tree-ssa/vectorization.html
  2. GCC documentation: Extensions to the C Language Family
    https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions
  3. GCC documentation: Using Vector Instructions through Built-in Functions
    https://gcc.gnu.org/online­docs/gcc/Vector-Extensions.html
  4. SSE (Streaming SIMD Extentions)
    http://www.songho.ca/misc/sse/sse­.html
  5. Timothy A. Chagnon: SSE and SSE2
    http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf
  6. Intel corporation: Extending the Worldr's Most Popular Processor Architecture
    http://download.intel.com/techno­logy/architecture/new-instructions-paper.pdf
  7. SIMD architectures:
    http://arstechnica.com/ol­d/content/2000/03/simd.ar­s/
  8. Tour of the Black Holes of Computing!: Floating Point
    http://www.cs.hmc.edu/~ge­off/classes/hmc.cs105…/sli­des/class02_floats.ppt
  9. 3Dnow! Technology Manual
    AMD Inc., 2000
  10. Intel MMXTM Technology Overview
    Intel corporation, 1996
  11. MultiMedia eXtensions
    http://softpixel.com/~cwrig­ht/programming/simd/mmx.phpi
  12. AMD K5 („K5“ / „5k86“)
    http://www.pcguide.com/ref/cpu/fam/g5K5-c.html
  13. Sixth Generation Processors
    http://www.pcguide.com/ref/cpu/fam/g6­.htm
  14. Great Microprocessors of the Past and Present
    http://www.cpushack.com/CPU/cpu1.html
  15. Very long instruction word (Wikipedia)
    http://en.wikipedia.org/wi­ki/Very_long_instruction_word
  16. CPU design (Wikipedia)
    http://en.wikipedia.org/wi­ki/CPU_design
  17. Bulldozer (microarchitecture)
    https://en.wikipedia.org/wi­ki/Bulldozer_(microarchitec­ture)
  18. SIMD Instructions Considered Harmful
    https://www.sigarch.org/simd-instructions-considered-harmful/
  19. GCC Compiler Intrinsics
    https://iq.opengenus.org/gcc-compiler-intrinsics/
  20. Scalable_Vector_Extension_(SVE)
    https://en.wikipedia.org/wi­ki/AArch64#Scalable_Vector_Ex­tension_(SVE)
  21. Improve the Multimedia User Experience
    https://www.arm.com/technologies/neon
  22. NEON Technology (stránky ARM)
    https://developer.arm.com/techno­logies/neon
  23. SIMD Assembly Tutorial: ARM NEON – Xiph.org
    https://people.xiph.org/~tte­rribe/daala/neon_tutorial­.pdf
  24. Ne10
    http://projectne10.github.io/Ne10/
  25. NEON and Floating-Point architecture
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/BABIGHEB.html
  26. An Introduction to ARM NEON
    http://peterdn.com/post/an-introduction-to-ARM-NEON.aspx
  27. ARM NEON Intrinsics Reference
    http://infocenter.arm.com/hel­p/topic/com.arm.doc.ihi0073a/I­HI0073A_arm_neon_intrinsic­s_ref.pdf
  28. Arm Neon Intrinsics vs hand assembly
    https://stackoverflow.com/qu­estions/9828567/arm-neon-intrinsics-vs-hand-assembly
  29. ARM NEON Optimization. An Example
    http://hilbert-space.de/?p=22
  30. AArch64 NEON instruction format
    https://developer.arm.com/doc­s/den0024/latest/7-aarch64-floating-point-and-neon/73-aarch64-neon-instruction-format
  31. ARM SIMD instructions
    https://developer.arm.com/do­cumentation/dht0002/a/Intro­ducing-NEON/What-is-SIMD-/ARM-SIMD-instructions
  32. Learn the architecture – Migrate Neon to SVE Version 1.0
    https://developer.arm.com/do­cumentation/102131/0100/?lan­g=en
  33. 1.2.2. Comparison between NEON technology and other SIMD solutions
    https://developer.arm.com/do­cumentation/den0018/a/Intro­duction/Comparison-between-ARM-NEON-technology-and-other-implementations/Comparison-between-NEON-technology-and-other-SIMD-solutions?lang=en
  34. NEON Programmer’s Guide
    https://documentation-service.arm.com/static/63299276e68c6809a6b4­1308
  35. 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/
  36. Other Built-in Functions Provided by GCC
    https://gcc.gnu.org/online­docs/gcc/Other-Builtins.html
  37. GCC: 6.60 Built-in Functions Specific to Particular Target Machines
    https://gcc.gnu.org/online­docs/gcc/Target-Builtins.html#Target-Builtins
  38. Advanced Vector Extensions
    https://en.wikipedia.org/wi­ki/Advanced_Vector_Extensi­ons
  39. Top 10 Craziest Assembly Language Instructions
    https://www.youtube.com/wat­ch?v=Wz_xJPN7lAY
  40. Intel x86: let's take a look at one of the most complex instruction set!
    https://www.youtube.com/wat­ch?v=KBLy23B38-c
  41. x64 Assembly Tutorial 58: Intro to AVX
    https://www.youtube.com/wat­ch?v=yAvuHd8cBJY
  42. AVX512 (1 of 3): Introduction and Overview
    https://www.youtube.com/watch?v=D-mM6X5×nTY
  43. AVX512 (2 of 3): Programming AVX512 in 3 Different Ways
    https://www.youtube.com/wat­ch?v=I3efQKLgsjM
  44. AVX512 (3 of 3): Deep Dive into AVX512 Mechanisms
    https://www.youtube.com/watch?v=543a1b-cPmU
  45. AVX-512
    https://en.wikipedia.org/wiki/AVX-512
  46. AVX-512
    https://iq.opengenus.org/avx512/
  47. SIMD Algorithms Youtube course
    https://denisyaroshevskiy­.github.io/presentations/
  48. Compiler explorer
    https://godbolt.org/
  49. Restricting pointers
    https://gcc.gnu.org/online­docs/gcc/Restricted-Pointers.html
  50. Does the restrict keyword provide significant benefits in gcc/g++
    https://stackoverflow.com/qu­estions/1965487/does-the-restrict-keyword-provide-significant-benefits-in-gcc-g
  51. Demystifying The Restrict Keyword
    https://cellperformance.be­yond3d.com/articles/2006/05/de­mystifying-the-restrict-keyword.html
  52. Basics of Vectorization for Fortran Applications
    https://inria.hal.science/hal-01688488/document
  53. What does the restrict keyword mean in C++?
    https://stackoverflow.com/qu­estions/776283/what-does-the-restrict-keyword-mean-in-c
  54. restrict keyword (Wikipedia)
    https://en.wikipedia.org/wi­ki/Restrict
  55. Reduction operator
    https://en.wikipedia.org/wi­ki/Reduction_operator
  56. The Power of the Dot Product in Artificial Intelligence
    https://medium.com/data-science/the-power-of-the-dot-product-in-artificial-intelligence-c002331e1829
  57. Can any one explain why dot product is used in neural network and what is the intitutive thought of dot product
    https://stats.stackexchan­ge.com/questions/291680/can-any-one-explain-why-dot-product-is-used-in-neural-network-and-what-is-the-in
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

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