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

18. 3. 2025
Doba čtení: 28 minut

Sdílet

Autor: Depositphotos
Už jsme se seznámili s instrukcemi SIMD, MMX, SSE a částečně i SSE2. Lze je volat z assembleru nebo využít takzvané intrinsic nabízené překladači. Ovšem existuje i další způsob jejich využití s automatickou vektorizací.

Obsah

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

2. První příklad: vynulování obsahu pole o předem známém počtu prvků

3. Výsledek překladu se zákazem i povolením SSE instrukcí: neoptimalizované varianty

4. Výsledek překladu se zákazem i povolením SSE instrukcí: optimalizované varianty a vliv zarovnání

5. Vliv změny velikosti nulovaného pole na kód vytvořený překladačem

6. Druhý příklad: přičtení konstantní hodnoty ke všem prvkům pole

7. Výsledek překladu se zákazem a povolením vektorizace

8. Vliv změny velikosti pole na kód vytvořený překladačem

9. Třetí příklad: přičtení obsahu jednoho pole k poli druhému

10. Výsledek překladu se zákazem a povolením vektorizace

11. Pomáháme překladači: klíčové slovo restrict

12. Kombinace obou nápověd: zarovnání i aliasu ukazatelů

13. Vliv změny velikosti pole na kód vytvořený překladačem

14. Výpočet druhé odmocniny všech prvků polí

15. Výsledek překladu se zákazem a povolením vektorizace

16. Vliv přepínače -ffast-math

17. Automatická vektorizace u operací typu reduce

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

Na stránkách Roota jsme se již několikrát setkali s instrukcemi typu SIMD, které dokážou provádět zvolené operace paralelně, například se čtyřmi prvky vektorů typu float nebo se šestnácti jednobajtovými prvky. Prozatím jsme si ukázali tři možnosti využití těchto instrukcí:

  • Tzv. vektorové rozšíření GCC o další datové typyv16us atd. Základní operace s těmito typy (což jsou vektory) jsou totiž prováděny právě SIMD instrukcemi.
  • Voláním takzvaných intrinsic zabudovaných přímo do překladače, které opět generují SIMD instrukce.
  • Naprogramováním příslušných subrutin v assembleru

Ovšem existuje ještě jeden způsob nabízený moderními překladači (a to nejenom překladači jazyka C). Jedná se o takzvanou autovektorizaci, což je sada optimalizací, které dokážou například převést některé formy programových smyček do podoby se SIMD instrukcemi. Ovšem v mnoha případech je nutné překladači vhodným způsobem pomoci, například specifikací modifikátoru restrict, uvedením, že pole jsou zarovnána na 8, 16 atd. bajtů atd.

V dnešním článku si některé tyto techniky ukážeme. Přitom se (prozatím) zaměříme na překladač GCC a jeho podporu autovektorizace. Ta se povoluje přepínačem -ftree-vectorize. Navíc se dnes omezíme pouze na SSE instrukce (ty jsou totiž poměrně dobře pochopitelné) a pole/vektory typu float/single, takže použijeme i přepínač -msse.

Poznámka: samozřejmě je možné použít GCC nainstalovaný přímo na počítači, ovšem výhodnější může být využití velmi propracovaného nástroje Compiler Explorer, který dokáže výstup do assembleru „očistit“ a nabídnout čitelnější výsledek. Pouze se přesvědčte, že máte v levém podokně s editorem zdrojového textu vybraný jazyk C a nikoli C++, protože jinak budou hlášeny problémy u těch demonstračních příkladů, v nichž je použito slovo restrict (v C++ se píše s podtržítky).

2. První příklad: vynulování obsahu pole o předem známém počtu prvků

Pokusy s autovektorizací začneme na triviálním příkladu. Jedná se o funkci, která vynuluje obsah pole hodnot typu float (v SIMD označovaných jako single podle IEEE 754). Počet prvků nulovaného pole je pro jednoduchost dopředu známý a je uložen v konstantě nazvané SIZE. Zdrojový kód, který vůbec nebere v úvahu případné optimalizace, vypadá následovně:

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

3. Výsledek překladu se zákazem i povolením SSE instrukcí: neoptimalizované varianty

Jak bude vypadat překlad této jednoduché funkce do strojového kódu mikroprocesorů s architekturou x86 (resp. přesněji řečeno x86–64)? Výsledky se v tomto případě budou zásadně lišit podle toho, zda je povoleno používat instrukce SSE nebo je to naopak zakázáno.

V případě, že jsou optimalizace zakázány a současně je zakázáno i použití SSE, bude výsledný kód používat instrukce FPU a vše bude realizováno v neoptimalizované smyčce:

clear(float*):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-24], rdi
        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
        fldz
        fstp    DWORD PTR [rax]
        add     DWORD PTR [rbp-4], 1
.L2:
        cmp     DWORD PTR [rbp-4], 7
        jle     .L3
        nop
        nop
        pop     rbp
        ret
Poznámka: povšimněte si použití instrukcí FLDZ (načtení nuly do FP registru) a FSTP (uložení FP registru do paměti). Jednou z nejjednodušších optimalizací je přesun první z těchto instrukcí mimo smyčku. A pochopitelně je možné i celou smyčku výrazně zkrátit.

Při zákazu optimalizací, ale povolení SSE je opět výsledkem neoptimalizovaná smyčka, v níž ale nalezneme registr XMM0 a skalární instrukci MOVSS:

clear(float*):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-24], rdi
        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
        pxor    xmm0, xmm0
        movss   DWORD PTR [rax], xmm0
        add     DWORD PTR [rbp-4], 1
.L2:
        cmp     DWORD PTR [rbp-4], 7
        jle     .L3
        nop
        nop
        pop     rbp
        ret
Poznámka: oba tyto kódy jsou skutečně velmi špatné a i začátečník v assembleru dokáže napsat optimálnější varianty.

4. Výsledek překladu se zákazem i povolením SSE instrukcí: optimalizované varianty a vliv zarovnání

V dalším kroku se pokusme o povolení optimalizací, prozatím s využitím přepínače -O2. V případě, že jsou instrukce SSE zakázány, bude zápis (na platformě x86–64) prováděn po 64bitových slovech, tj. po dvojici hodnot typu float. To je již mnohem rychlejší a není zapotřebí realizovat pomalou počítanou programovou smyčku:

clear(float*):
        mov     QWORD PTR [rdi], 0
        mov     QWORD PTR [rdi+8], 0
        mov     QWORD PTR [rdi+16], 0
        mov     QWORD PTR [rdi+24], 0
        ret

Při povolení instrukcí SSE je kód odlišný. Nejdříve se smaže obsah 128bitového registru XMM0 a následně se provedou pouze dva zápisy, z nichž každý uloží do paměti čtyři hodnoty typu float:

clear(float*):
        pxor    xmm0, xmm0
        movups  XMMWORD PTR [rdi], xmm0
        movups  XMMWORD PTR [rdi+16], xmm0
        ret

Překladač jazyka C v tomto případě neví, jak budou prvky pole (ne)zarovnány a proto musí použít instrukci MOVUPS a nikoli MOVAPS. Ovšem pokud existuje jistota, že pole začíná na zarovnané adrese, je možné překladači „napovědět“:

void clear(float *a) {
    #define SIZE 8
    a = __builtin_assume_aligned (a, 16);
    int i;
    for (i=0; i<SIZE; i++) {
        a[i] = 0.0;
    }
}

Výsledkem nyní bude obecně rychlejší kód s instrukcemi MOVAPS namísto MOVUPS:

clear(float*):
        pxor    xmm0, xmm0
        movaps  XMMWORD PTR [rdi], xmm0
        movaps  XMMWORD PTR [rdi+16], xmm0
        ret

5. Vliv změny velikosti nulovaného pole na kód vytvořený překladačem

Předchozí zdrojový kód byl pro překladač snadným úkolem, protože délka nulovaného pole byla rovna osmi prvkům. Co se však stane ve chvíli, kdy tuto délku pozměníme? Nejprve nastavíme SIZE na hodnotu 16 a provedeme překlad s využitím SSE instrukcí. Výsledkem je stále „rozbalená“ smyčka

clear(float*):
        pxor    xmm0, xmm0
        movups  XMMWORD PTR [rdi], xmm0
        movups  XMMWORD PTR [rdi+16], xmm0
        movups  XMMWORD PTR [rdi+32], xmm0
        movups  XMMWORD PTR [rdi+48], xmm0
        ret

Pro SIZE nastavené na hodnotu 18 již není možné (alespoň ne jednoduše) všechny prvky zapsat instrukcemi MOVUPS nebo MOVAPS, protože 18 není dělitelná čtyřmi, Překladač si poradí tak, že 16 prvků zapíše po čtveřicích a další dva prvky jako jediné čtyřslovo:

clear(float*):
        pxor    xmm0, xmm0
        mov     QWORD PTR [rdi+64], 0
        movups  XMMWORD PTR [rdi], xmm0
        movups  XMMWORD PTR [rdi+16], xmm0
        movups  XMMWORD PTR [rdi+32], xmm0
        movups  XMMWORD PTR [rdi+48], xmm0
        ret

A pro SIZE nastavené na 17 prvků dopadne výsledek takto (poslední prvek je zapsán jako dvojslovo):

clear(float*):
        pxor    xmm0, xmm0
        mov     DWORD PTR [rdi+64], 0
        movaps  XMMWORD PTR [rdi], xmm0
        movaps  XMMWORD PTR [rdi+16], xmm0
        movaps  XMMWORD PTR [rdi+32], xmm0
        movaps  XMMWORD PTR [rdi+48], xmm0
        ret

6. Druhý příklad: přičtení konstantní hodnoty ke všem prvkům pole

V dnešním druhém ukázkovém příkladu je realizována funkce, která ke všem prvkům pole o známé délce přičítá konstantu předanou formou argumentu. Celý příklad je naprogramován naivním způsobem, bez jakékoli snahy o optimalizace na úrovni céčkovského zdrojového kódu:

void add_delta(float *a, float delta) {
    #define SIZE 16
    int i;
    for (i=0; i<SIZE; i++) {
        a[i] += delta;
    }
}

7. Výsledek překladu se zákazem a povolením vektorizace

Nejprve se podívejme na výsledek překladu v situaci, kdy není automatická vektorizace povolena. V tomto případě se sice používají SSE instrukce a registry SSE (XMM0, XMM1), ovšem realizace přičtení konstanty používá programovou smyčku se skalárními instrukcemi ADDSS a MOVSS (ty dobře známe):

add_delta(float*, float):
        lea     rax, [rdi+64]
.L2:
        movss   xmm1, DWORD PTR [rdi]
        add     rdi, 4
        addss   xmm1, xmm0
        movss   DWORD PTR [rdi-4], xmm1
        cmp     rdi, rax
        jne     .L2
        ret

Vektorizovaná varianta je opět realizována programovou smyčkou, ovšem nyní se v každé iteraci provede přičtení konstanty ke čtyřem prvkům pole a počet opakování smyčky je čtvrtinový. Díky tomu, že je délka pole dělitelná čtyřmi, je tato optimalizace zcela legální. Za povšimnutí stojí především použití instrukce SHUFPS, v níž je zdrojový i cílový registr totožný. Díky tomu se nejnižší prvek registru XMM0 rozkopíruje do ostatních tří prvků, což je trik, který je užitečné znát:

add_delta(float*, float):
        lea     rax, [rdi+64]
        shufps  xmm0, xmm0, 0
.L2:
        movups  xmm1, XMMWORD PTR [rdi]
        add     rdi, 16
        addps   xmm1, xmm0
        movups  XMMWORD PTR [rdi-16], xmm1
        cmp     rax, rdi
        jne     .L2
        ret

8. Vliv změny velikosti pole na kód vytvořený překladačem

A jak bude vypadat výsledný kód při změně velikosti pole? Nejprve si ukažme výsledek překladu pro SIZE nastavenou na hodnotu 24 (což je stále dělitelné čtyřmi). Opět se použije plně optimalizovaná varianta (nerozbalené) programové smyčky se součtem vektorů:

add_delta(float*, float):
        lea     rax, [rdi+96]
        shufps  xmm0, xmm0, 0
.L2:
        movups  xmm1, XMMWORD PTR [rdi]
        add     rdi, 16
        addps   xmm1, xmm0
        movups  XMMWORD PTR [rdi-16], xmm1
        cmp     rax, rdi
        jne     .L2
        ret

Zajímavá situace nastane pro SIZE nastavenou na hodnotu 17, což v žádném případě není dělitelné čtyřmi. V takovém případě se překladač bude snažit o rozdělení zápisu s využitím součtu vektorů ve smyčce. A zbývající jeden až tři součty budou provedeny až za touto smyčkou:

add_delta(float*, float):
        movaps  xmm2, xmm0
        mov     rax, rdi
        lea     rdx, [rdi+64]
        shufps  xmm2, xmm2, 0
.L2:
        movups  xmm1, XMMWORD PTR [rax]
        add     rax, 16
        addps   xmm1, xmm2
        movups  XMMWORD PTR [rax-16], xmm1
        cmp     rdx, rax
        jne     .L2
        addss   xmm0, DWORD PTR [rdi+64]
        movss   DWORD PTR [rdi+64], xmm0
        ret

Pro SIZE nastavené na hodnotu 18 se po dokončení smyčky provede součet dvou prvků s využitím součtu vektorů:

add_delta(float*, float):
        movaps  xmm2, xmm0
        mov     rax, rdi
        lea     rdx, [rdi+64]
        shufps  xmm2, xmm2, 0
.L2:
        movups  xmm1, XMMWORD PTR [rax]
        add     rax, 16
        addps   xmm1, xmm2
        movups  XMMWORD PTR [rax-16], xmm1
        cmp     rdx, rax
        jne     .L2
        movq    xmm1, QWORD PTR [rdi+64]
        shufps  xmm0, xmm0, 0xe0
        movq    xmm0, xmm0
        addps   xmm0, xmm1
        movlps  QWORD PTR [rdi+64], xmm0
        ret

Dtto pro velikost 19:

add_delta(float*, float):
        movaps  xmm2, xmm0
        mov     rax, rdi
        lea     rdx, [rdi+64]
        shufps  xmm2, xmm2, 0
.L2:
        movups  xmm1, XMMWORD PTR [rax]
        add     rax, 16
        addps   xmm1, xmm2
        movups  XMMWORD PTR [rax-16], xmm1
        cmp     rdx, rax
        jne     .L2
        movq    xmm1, QWORD PTR [rdi+64]
        movaps  xmm2, xmm0
        addss   xmm0, DWORD PTR [rdi+72]
        shufps  xmm2, xmm2, 0xe0
        movq    xmm2, xmm2
        addps   xmm1, xmm2
        movss   DWORD PTR [rdi+72], xmm0
        movlps  QWORD PTR [rdi+64], xmm1
        ret
Poznámka: z toho plyne první poučení – je více než vhodné, aby délka polí byla dělitelná čtyřmi, ale ideálně osmi nebo šestnácti (AVX/512). Výsledný kód bude kratší a je větší pravděpodobnost, že celý zůstane v L1 cache.

9. Třetí příklad: přičtení obsahu jednoho pole k poli druhému

Třetí příklad, který se pokusíme přeložit do strojového kódu ve vektorizované podobě, provádí přičtení obsahu jednoho pole typu float ke druhému poli typu float. Sčítají se odpovídající si prvky pole, tedy nultý s nultým, první s prvním atd. Opět si nejdříve ukažme naivní variantu zápisu tohoto algoritmu, ve které se nijak nesnažíme překladači pomoci přidáním dalších informací:

void add_arrays(float *a, float *b) {
    #define SIZE 4
    int i;
    for (i=0; i<SIZE; i++) {
        a[i] += b[i];
    }
}

10. Výsledek překladu se zákazem a povolením vektorizace

Překlad se zákazem automatické vektorizace vede k vytvoření programové smyčky, ve které se postupně provádí skalární součet s využitím instrukce addss. Jedná se sice o poměrně primitivní způsob překladu, ovšem jeho výhodou je, že není zapotřebí zjišťovat překryvy polí atd.:

add_arrays(float*, float*):
        xor     eax, eax
.L2:
        movss   xmm0, DWORD PTR [rdi+rax]
        addss   xmm0, DWORD PTR [rsi+rax]
        movss   DWORD PTR [rdi+rax], xmm0
        add     rax, 4
        cmp     rax, 96
        jne     .L2
        ret

Při povolení vektorizace se vygeneruje poměrně dlouhý programový kód:

add_arrays(float*, float*):
        lea     rdx, [rsi+4]
        mov     rax, rdi
        sub     rax, rdx
        cmp     rax, 8
        mov     eax, 0
        jbe     .L2
.L3:
        movups  xmm0, XMMWORD PTR [rdi+rax]
        movups  xmm1, XMMWORD PTR [rsi+rax]
        addps   xmm0, xmm1
        movups  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 96
        jne     .L3
        ret
.L2:
        movss   xmm0, DWORD PTR [rdi+rax]
        addss   xmm0, DWORD PTR [rsi+rax]
        movss   DWORD PTR [rdi+rax], xmm0
        add     rax, 4
        cmp     rax, 96
        jne     .L2
        ret

Ovšem proč tomu tak je? Překladač si nemůže být jistý, že se předaná pole nepřekrývají. Musí tedy provést kontrolu, jestli nedochází k překryvu (o čtyři prvky) a pokud ano, provede se neoptimalizovaná „skalární“ část algoritmu. Tyto části jsou dobře viditelné. „Vektorová část“ provádějící součty po čtveřicích:

.L3:
        movups  xmm0, XMMWORD PTR [rdi+rax]
        movups  xmm1, XMMWORD PTR [rsi+rax]
        addps   xmm0, xmm1
        movups  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 96
        jne     .L3
        ret

Skalární část:

.L2:
        movss   xmm0, DWORD PTR [rdi+rax]
        addss   xmm0, DWORD PTR [rsi+rax]
        movss   DWORD PTR [rdi+rax], xmm0
        add     rax, 4
        cmp     rax, 96
        jne     .L2
        ret

11. Pomáháme překladači: klíčové slovo restrict

V případě, že sčítáme dvě rozdílná pole, by bylo vhodné překladači nějakým způsobem napovědět, že nemusí do výsledného kódu vkládat i variantu s překrývajícími se poli. Pokud by byla pole odlišného typu (resp. pokud by ukazatele byly odlišného typu), je řešení snadné – podle normy je chování překladače nedefinované :-). Ovšem naše pole mají shodný typ (přesněji řečeno oba ukazatele mají totožný typ float *), takže překladač musí předpokládat, že se může jednat o stejné ukazatele nebo že se budou oblasti překrývat. Jak překladači napovědět, že k této situaci nedojde a nemusí se jí zabývat? V jazyku C, konkrétně ve verzi C99 a vyšší, je pro tento účel rezervováno slovo restrict, které se používá následujícím způsobem:

void add_arrays(float *restrict a, float * restrict b) {
    #define SIZE 4
    int i;
    for (i=0; i&tl;SIZE; i++) {
        a[i] += b[i];
    }
}

Překladač v tomto případě může provést optimalizace předpokládající, že se sčítají odlišná pole a výsledek bude mnohem kratší:

add_arrays(float* restrict, float* restrict):
        movups  xmm1, XMMWORD PTR [rdi]
        movups  xmm0, XMMWORD PTR [rsi]
        addps   xmm0, xmm1
        movups  XMMWORD PTR [rdi], xmm0
        ret
Poznámka: právě přidáním slova restrict byly umožněny optimalizace, které byly zavedeny o dvě dekády dříve do Fortranu, ale v céčku je nebylo možné standardním způsobem provádět.

12. Kombinace obou nápověd: zarovnání i aliasu ukazatelů

Samozřejmě můžeme zkombinovat obě nápovědy, které předáváme překladači, tj. informaci o zarovnání polí i informaci o tom, že ukazatele obsahují adresu rozdílných polí:

void add_arrays(float *restrict a, float * restrict b) {
    a = __builtin_assume_aligned (a, 16);
    b = __builtin_assume_aligned (b, 16);
 
    #define SIZE 4
    int i;
    for (i=0; i<SIZE; i++) {
        a[i] += b[i];
    }
}

Výsledek nyní obsahuje instrukci MOVAPS pro čtení a zápis do „zarovnané“ oblasti paměti. Nejenom to – výsledek je kratší o jednu instrukci, protože ADDPS může jako druhý operand mít zarovnanou adresu a nikoli pouze XMM registr:

add_arrays(float* restrict, float* restrict):
        movaps  xmm0, XMMWORD PTR [rdi]
        addps   xmm0, XMMWORD PTR [rsi]
        movaps  XMMWORD PTR [rdi], xmm0
        ret

13. Vliv změny velikosti pole na kód vytvořený překladačem

Pro delší pole (SIZE je nastaveno na větší hodnotu) s počtem prvků dělitelných šestnácti můžeme při povolení automatických vektorizací a současně i zajištění zarovnání polí atd. získat tento výsledek se smyčkou, ve které se vždy sčítají čtveřice prvků vektorů:

add_arrays(float* restrict, float* restrict):
        xor     eax, eax
.L2:
        movaps  xmm0, XMMWORD PTR [rdi+rax]
        addps   xmm0, XMMWORD PTR [rsi+rax]
        movaps  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 64
        jne     .L2
        ret

Pokud počet prvků není dělitelný čtyřmi, například je nastaven na 17, musí se do kódu vložit i instrukce pro dokončení výpočtů pro poslední 1 až 3 prvky. To je patrné z následujícího kódu pro SIZE=17:

add_arrays(float*, float*):
        xor     eax, eax
.L2:
        movaps  xmm0, XMMWORD PTR [rdi+rax]
        addps   xmm0, XMMWORD PTR [rsi+rax]
        movaps  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 64
        jne     .L2
        movss   xmm0, DWORD PTR [rdi+64]
        addss   xmm0, DWORD PTR [rsi+64]
        movss   DWORD PTR [rdi+64], xmm0
        ret
Poznámka: podobně by se postupovalo pro SIZE=18 nebo SIZE=19 atd.

Jen pro ilustraci doplňme, jak dlouhý kód musí být vygenerován ve chvíli, kdy překladač nemá informace o zarovnání ani o aliasu ukazatelů:

add_arrays(float*, float*):
        lea     rdx, [rsi+4]
        mov     rax, rdi
        sub     rax, rdx
        cmp     rax, 8
        mov     eax, 0
        jbe     .L2
.L3:
        movups  xmm0, XMMWORD PTR [rdi+rax]
        movups  xmm1, XMMWORD PTR [rsi+rax]
        addps   xmm0, xmm1
        movups  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 64
        jne     .L3
        movss   xmm0, DWORD PTR [rdi+64]
        addss   xmm0, DWORD PTR [rsi+64]
        movss   DWORD PTR [rdi+64], xmm0
        ret
.L2:
        movss   xmm0, DWORD PTR [rdi+rax]
        addss   xmm0, DWORD PTR [rsi+rax]
        movss   DWORD PTR [rdi+rax], xmm0
        add     rax, 4
        cmp     rax, 68
        jne     .L2
        ret

14. Výpočet druhé odmocniny všech prvků polí

Automatická vektorizace kódu může proběhnout i ve chvíli, kdy se pokusíme o překlad funkce, ve které se pro všechny prvky pole počítá například jejich převrácená hodnota nebo druhá odmocnina. Je tomu tak z toho důvodu, že v instrukční sadě SSE existují příslušné SIMD instrukce SQRTSS/SQRTPS, RSQRTSS/RSQRTPS a RCPSS/RCPPS určené pro přesný výpočet či o „odhad“ výsledku s předem známou přesností. Pokusme se tedy přeložit následující funkci se snadno pochopitelným algoritmem:

#include <math.h>
 
void array_sqrt(float *a) {
    #define SIZE 24
    int i;
    for (i=0; i<SIZE; i++) {
        a[i] = sqrt(a[i]);
    }
}
Poznámka: nyní je délka pole nastavena na relativně vysokou hodnotu, abychom překladač „přesvědčili“ o tom, že je vhodné použít programovou smyčku (i když by bylo možné tuto smyčku rozbalit).

15. Výsledek překladu se zákazem a povolením vektorizace

Výsledek překladu bude značně záviset na použitých přepínačích. Při zákazu vektorizace dostaneme tento výsledek:

array_sqrt(float*):
        push    rbx
        pxor    xmm1, xmm1
        lea     rbx, [rdi+96]
        sub     rsp, 16
.L5:
        movss   xmm0, DWORD PTR [rdi]
        ucomiss xmm1, xmm0
        ja      .L8
        sqrtss  xmm0, xmm0
.L4:
        movss   DWORD PTR [rdi], xmm0
        add     rdi, 4
        cmp     rdi, rbx
        jne     .L5
        add     rsp, 16
        pop     rbx
        ret
.L8:
        mov     QWORD PTR [rsp+8], rdi
        call    sqrtf
        mov     rdi, QWORD PTR [rsp+8]
        pxor    xmm1, xmm1
        jmp     .L4

Vygenerovaný kód je tak dlouhý z toho důvodu, že se v případě výpočtu druhé odmocniny (například) ze záporných čísel musí nastavit errno atd. Pokud tuto hodnotu (tedy test, jak dopadly předchozí FPU operace) stejně v našem algoritmu nepoužijeme, lze překladači přes přepínač -fno-math-errno sdělit, že případné chyby nemusí zaznamenávat. Výsledný kód bude kratší a čitelnější:

array_sqrt(float*):
        lea     rax, [rdi+96]
.L2:
        movss   xmm0, DWORD PTR [rdi]
        add     rdi, 4
        sqrtss  xmm0, xmm0
        movss   DWORD PTR [rdi-4], xmm0
        cmp     rdi, rax
        jne     .L2
        ret

Povolením vektorizace získáme ještě optimálnější variantu, ve které se provádí výpočty druhé odmocniny vždy pro čtyři hodnoty současně:

array_sqrt(float*):
        lea     rax, [rdi+96]
.L2:
        movups  xmm1, XMMWORD PTR [rdi]
        add     rdi, 16
        sqrtps  xmm0, xmm1
        movups  XMMWORD PTR [rdi-16], xmm0
        cmp     rax, rdi
        jne     .L2
        ret

16. Vliv přepínače -ffast-math

Mohlo by se zdát, že v předchozí kapitole uvedený vektorizovaný kód již není možné žádným způsobem vylepšit. Ovšem v případě, že jsou vyžadovány skutečně velmi rychlé výpočty, může být užitečné použít přepínač -ffast-math, který překladači umožní vygenerovat instrukce, které nebudou provádět výpočty přesně podle normy IEEE 754. Výsledkem bude v našem konkrétním případě kód, ve kterém se nejprve provede přibližný (ale o to rychlejší) výpočet převrácené hodnoty druhé odmocniny a poté se z této hodnoty (a s využitím dvou vhodně zvolených konstant) dopočítají druhé odmocniny:

array_sqrt(float*):
        movss   xmm5, DWORD PTR .LC1[rip]
        movss   xmm4, DWORD PTR .LC3[rip]
        lea     rax, [rdi+96]
        pxor    xmm3, xmm3
        shufps  xmm5, xmm5, 0
        shufps  xmm4, xmm4, 0
.L2:
        movups  xmm1, XMMWORD PTR [rdi]
        movaps  xmm2, xmm3
        add     rdi, 16
        rsqrtps xmm0, xmm1
        cmpneqps        xmm2, xmm1
        andps   xmm0, xmm2
        mulps   xmm1, xmm0
        mulps   xmm0, xmm1
        mulps   xmm1, xmm4
        addps   xmm0, xmm5
        mulps   xmm0, xmm1
        movups  XMMWORD PTR [rdi-16], xmm0
        cmp     rax, rdi
        jne     .L2
        ret
.LC1:
        .long   -1069547520
.LC3:
        .long   -1090519040
Poznámka: benchmark, který ověří, že je tento kód skutečně rychlejší, než pouhé volání SQRTPS, si uvedeme příště.

17. Automatická vektorizace u operací typu reduce

Většina demonstračních příkladů, které jsme si až doposud uvedli, vektorizovala programový kód zapsaný s využitím počítané programové smyčky, ve které se postupně prochází (a popř. modifikují) prvky jednoho pole nebo dvou polí. Ovšem současné verze překladačů dokážou vektorizovat i takové programové smyčky, v nichž se například prvky sčítají, počítá se skalární součin dvou vektorů, vyhledává se nejnižší nebo nejvyšší hodnota atd. Takové operace se nazývají operace typu reduce a se způsobem jejich vektorizace se seznámíme příště.

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

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/

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

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »