Hlavní navigace

Užitečné rozšíření GCC – podpora SIMD (vektorových) instrukcí: nedostatky technologie

11. 10. 2022
Doba čtení: 50 minut

Sdílet

 Autor: Depositphotos
Podpora SIMD operací v rozšíření GCC je ve skutečnosti pouze částečná a má mnohé nedostatky, o nichž se dnes zmíníme. Taktéž si ukážeme, že vektory zavedené v rámci tohoto rozšíření není vhodné slepě používat namísto polí.

Obsah

1. Užitečné rozšíření GCC – podpora SIMD (vektorových) instrukcí: nedostatky

2. Vyplatí se používat dlouhé vektory?

3. Předání vektorů do funkce hodnotou vs. referencí

4. Způsob překladu vektorových operací do assembleru

5. Tytéž operace, ovšem prováděné s prvky se znaménkem

6. Způsob překladu vektorových operací do assembleru

7. Vektorové operace nad vektory s prvky typu floatdouble

8. Překlad do assembleru

9. Vektor není libovolně velké pole – omezení GCC

10. Způsob překladu operace s dlouhým vektorem do assembleru

11. Ne všechny operace mohou být v SIMD podporovány

12. Výsledek překladu předchozího programu do kódu se SIMD operacemi

13. Základní aritmetické operace s prvky vektorů typu float

14. Překlad bez použití instrukcí SIMD

15. Překlad do strojového kódu s použitím instrukcí SIMD

16. Zásadní omezení rozšíření (sic) GCC

17. SIMD a vektorové operace na architekturách AArch64 a RISC-V

18. Příloha – soubor Makefile použitý v dnešním článku

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

20. Odkazy na Internetu

1. Užitečné rozšíření GCC – podpora SIMD (vektorových) instrukcí: nedostatky

Na úvodní článek o problematice využití SIMD operací na moderních mikroprocesorech v jazyku C dnes navážeme. Prozatím jsme si řekli, jaké SIMD operace můžeme nalézt v rozšíření instrukčních sad pojmenovaných MMX, 3DNow!, SSE a SSE2, což jsou (kromě zastaralé 3DNow!) rozšíření, která nalezneme v prakticky všech dnes používaných čipech s architekturou x86 (x86–64) a bylo by dobré je začít ve vyšší míře nějakým způsobem používat. Taktéž víme, že rozšíření překladače GCC C dokáže do určité míry tyto rozšířené instrukční sady skutečně využívat, protože programátorům nabízí základní aritmetické a logické operace prováděné s vektory.

Ovšem toto řešení je ve skutečnosti pouze částečné, neboť má mnohé nedostatky, o nichž se zmíníme v dnešním článku. Taktéž si ukážeme, že vektory zavedené v rámci tohoto rozšíření není vhodné slepě používat namísto polí (o předem zadané délce), neboť to nemusí vést k optimálnímu kódu.

2. Vyplatí se používat dlouhé vektory?

Podívejme se nyní na následující demonstrační příklad, v němž je použit relativně dlouhý vektor s celkem 256/4 = 64 prvky typu float. Vektor zde tedy používáme ve funkci pole:

typedef float v256f __attribute__((vector_size(256)));
 
int main(void)
{
    v256f x = { 1.0 };
    v256f y = { 1.0 };
    v256f z = x + y;
 
    return 0;
}

Tento demonstrační příklad je sice plně funkční, ovšem i tak je potenciálně problematický. Na jeden problém narazíme vcelku rychle – tímto způsobem lze totiž tvořit a používat pouze vektory s délkou, jenž je celočíselnou mocninou dvojky. O tomto omezení se můžeme snadno přesvědčit ve chvíli, kdy se pokusíme délku vektoru zvětšit o jeden prvek typu float, tedy celkem o čtyři bajty (z 256 bajtů na 260 bajtů):

typedef float v260f __attribute__((vector_size(260)));
 
int main(void)
{
    v260f x = { 1.0 };
    v260f y = { 1.0 };
    v260f z = x + y;
 
    return 0;
}

Při pokusu o překlad tohoto zdrojového kódu nahlásí překladač chybu:

simd12B.c:1:1: error: number of components of the vector not a power of two
    1 | typedef float v260f __attribute__((vector_size(260)));
      | ^~~~~~~

Ovšem problémů je ve skutečnosti více – vektory se implicitně předávají hodnotou a nikoli referencí (jako pole), vektorové operace se (na x86–64) vždy provádí sekvencí instrukcí bez smyček atd. Tyto potenciální problémy, které vlastně rostou s délkou vektorů, si popíšeme v navazujících kapitolách.

3. Předání vektorů do funkce hodnotou vs. referencí

Pole se v programovacím jazyku C předávají referencí, o čemž se můžeme velmi snadno přesvědčit překladem a spuštěním následujícího demonstračního příkladu:

#include <stdio.h>
 
typedef int array[4];
 
void foo(array a)
{
    a[1] = 42;
}
 
int main(void)
{
    array x = { 1, 2, 3, 4 };
 
    printf("%d %d %d %d\n", x[0], x[1], x[2], x[3]);
 
    foo(x);
    printf("%d %d %d %d\n", x[0], x[1], x[2], x[3]);
}

Pokud by se pole předávalo hodnotou, pracovala by funkce foo s kopií pole a zobrazené zprávy by měly vypadat takto:

1 2 3 4
1 2 3 4

Ve skutečnosti se pole předává referencí a proto je možné ve funkci foo modifikovat jeho prvky:

1 2 3 4
1 42 3 4

U vektorů je tomu však jinak, o čemž se opět můžeme velmi snadno přesvědčit:

#include <stdio.h>
 
typedef int vector __attribute__((vector_size(16)));
 
void foo(vector v)
{
    v[1] = 42;
}
 
int main(void)
{
    vector x = { 1, 2, 3, 4 };
 
    printf("%d %d %d %d\n", x[0], x[1], x[2], x[3]);
 
    foo(x);
    printf("%d %d %d %d\n", x[0], x[1], x[2], x[3]);
}

Zde při překladu a spuštění dostaneme:

1 2 3 4
1 2 3 4

Tento výsledek ukazuje, že se vektory předávají hodnotou, což může být náročná operace, zejména u delších vektorů.

4. Způsob překladu vektorových operací do assembleru

Vyzkoušejme si nyní, jak se přeloží funkce provádějící součet vektorů do assembleru. Vše si otestujeme na funkcích, které sčítají vektory s prvky typu unsigned char, unsigned short, unsigned int a unsigned long:

#include <stdio.h>
 
typedef unsigned char v16ub __attribute__((vector_size(16)));
 
void add16ub(v16ub x, v16ub y, v16ub * z)
{
    *z = x + y;
}
 
typedef unsigned short v16us __attribute__((vector_size(16)));
 
void add16us(v16us x, v16us y, v16us * z)
{
    *z = x + y;
}
 
typedef unsigned int v16ui __attribute__((vector_size(16)));
 
void add16ui(v16ui x, v16ui y, v16ui * z)
{
    *z = x + y;
}
 
typedef unsigned long int v16ul __attribute__((vector_size(16)));
 
void add16ul(v16ul x, v16ul y, v16ul * z)
{
    *z = x + y;
}
 
int main(void)
{
    {
        v16ub x = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
        v16ub y =
            { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        };
        v16ub z;
 
        add16ub(x, y,  z);
 
        int i;
 
        puts("vector of unsigned chars");
 
        for (i = 0; i < sizeof(v16ub) / sizeof(unsigned char); i++) {
            printf("%d %u\n", i, z[i]);
        }
    }
 
    putchar('\n');
 
    {
        v16us x = { 0, 1, 2, 3, 4, 5, 6, 7 };
        v16us y = { 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
            0xffff
        };
        v16us z;
 
        add16us(x, y,  z);
 
        int i;
 
        puts("vector of unsigned short ints");
 
        for (i = 0; i < sizeof(v16us) / sizeof(unsigned short); i++) {
            printf("%d %u\n", i, z[i]);
        }
    }
 
    putchar('\n');
 
    {
        v16ui x = { 0, 1, 2, 3 };
        v16ui y = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff };
        v16ui z;
 
        add16ui(x, y,  z);
 
        int i;
 
        puts("vector of unsigned ints");
 
        for (i = 0; i < sizeof(v16ui) / sizeof(unsigned int); i++) {
            printf("%d %u\n", i, z[i]);
        }
    }
 
    putchar('\n');
 
    {
        v16ul x = { 0, 1 };
        v16ul y = { 0xffffffffffffffff, 0xffffffffffffffff };
        v16ul z;
 
        add16ul(x, y,  z);
 
        int i;
 
        puts("vector of unsigned longs");
 
        for (i = 0; i < sizeof(v16ul) / sizeof(unsigned long); i++) {
            printf("%d %lu\n", i, z[i]);
        }
    }
 
    return 0;
}

S výsledky:

vector of unsigned chars
0 255
1 0
2 1
3 2
4 3
5 4
6 5
7 6
8 7
9 8
10 9
11 10
12 11
13 12
14 13
15 14
 
vector of unsigned short ints
0 65535
1 0
2 1
3 2
4 3
5 4
6 5
7 6
 
vector of unsigned ints
0 4294967295
1 0
2 1
3 2
 
vector of unsigned longs
0 18446744073709551615
1 0
Poznámka: povšimněte si přetečení výsledků, které je očekávatelné.

Překlad výše uvedeného kódu do assembleru vypadá takto.

Nejdříve se při zákazu SIMD operací provede maskování bajtů, 16bitových slov nebo 32bitových slov a paralelní výpočty v rámci 64bitových registrů. Tím se simuluje součet po bajtech, 16bitových slovech a 32bitových slovech:

   c:   48 8b 45 10             mov    rax,QWORD PTR [rbp+0x10]
  10:   48 8b 55 20             mov    rdx,QWORD PTR [rbp+0x20]
  14:   48 89 c6                mov    rsi,rax
  17:   48 31 d6                xor    rsi,rdx
  1a:   48 b9 7f 7f 7f 7f 7f    movabs rcx,0x7f7f7f7f7f7f7f7f
  21:   7f 7f 7f
  24:   48 21 d1                and    rcx,rdx
  27:   48 ba 7f 7f 7f 7f 7f    movabs rdx,0x7f7f7f7f7f7f7f7f
  2e:   7f 7f 7f
  31:   48 21 d0                and    rax,rdx
  34:   48 ba 80 80 80 80 80    movabs rdx,0x8080808080808080
  3b:   80 80 80
  3e:   48 21 f2                and    rdx,rsi
  41:   48 01 c8                add    rax,rcx
  44:   48 89 d6                mov    rsi,rdx
  47:   48 31 c6                xor    rsi,rax
  4a:   48 8b 45 18             mov    rax,QWORD PTR [rbp+0x18]
  4e:   48 8b 55 28             mov    rdx,QWORD PTR [rbp+0x28]
  52:   48 89 c7                mov    rdi,rax
  55:   48 31 d7                xor    rdi,rdx
  58:   48 b9 7f 7f 7f 7f 7f    movabs rcx,0x7f7f7f7f7f7f7f7f
  5f:   7f 7f 7f
  62:   48 21 d1                and    rcx,rdx
  65:   48 ba 7f 7f 7f 7f 7f    movabs rdx,0x7f7f7f7f7f7f7f7f
  6c:   7f 7f 7f
  6f:   48 21 d0                and    rax,rdx
  72:   48 ba 80 80 80 80 80    movabs rdx,0x8080808080808080
  79:   80 80 80
  7c:   48 21 fa                and    rdx,rdi
  7f:   48 01 c8                add    rax,rcx
  82:   48 89 d1                mov    rcx,rdx
  85:   48 31 c1                xor    rcx,rax
  88:   b8 00 00 00 00          mov    eax,0x0
  8d:   ba 00 00 00 00          mov    edx,0x0
  92:   48 89 f0                mov    rax,rsi
  95:   48 89 ca                mov    rdx,rcx
  98:   48 8b 4d f8             mov    rcx,QWORD PTR [rbp-0x8]
  9c:   48 89 01                mov    QWORD PTR [rcx],rax
  9f:   48 89 51 08             mov    QWORD PTR [rcx+0x8],rdx
Poznámka: výše uvedená ukázka platí pro součet prvků typu unsigned char. Podobně – akorát se širší maskou – tomu bude u prvků typu unsigned shortunsigned int.

Nás však zajímají „vektorové“ výpočty.

Součet vektorů s prvky typu unsigned char zajišťuje instrukce PADDB:

   8:   0f 29 45 f0             movaps XMMWORD PTR [rbp-0x10],xmm0
   c:   0f 29 4d e0             movaps XMMWORD PTR [rbp-0x20],xmm1
  10:   48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
    *z = x + y;
  14:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  19:   66 0f fc 45 e0          paddb  xmm0,XMMWORD PTR [rbp-0x20]
  1e:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  22:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Součet vektorů s prvky typu unsigned short (neboli v assembleru „word“) zajišťuje instrukce PADDW:

  30:   0f 29 45 f0             movaps XMMWORD PTR [rbp-0x10],xmm0
  34:   0f 29 4d e0             movaps XMMWORD PTR [rbp-0x20],xmm1
  38:   48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
    *z = x + y;
  3c:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  41:   66 0f fd 45 e0          paddw  xmm0,XMMWORD PTR [rbp-0x20]
  46:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  4a:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Součet po 32bitových „dvouslovech“ („double word“) je vykonán instrukcí PADDD:

  58:   0f 29 45 f0             movaps XMMWORD PTR [rbp-0x10],xmm0
  5c:   0f 29 4d e0             movaps XMMWORD PTR [rbp-0x20],xmm1
  60:   48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
    *z = x + y;
  64:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  69:   66 0f fe 45 e0          paddd  xmm0,XMMWORD PTR [rbp-0x20]
  6e:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  72:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

A konečně součet po „čtyřslovech“ („quad word“) je realizován instrukcí PADDQ:

  80:   0f 29 45 f0             movaps XMMWORD PTR [rbp-0x10],xmm0
  84:   0f 29 4d e0             movaps XMMWORD PTR [rbp-0x20],xmm1
  88:   48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
    *z = x + y;
  8c:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  91:   66 0f d4 45 e0          paddq  xmm0,XMMWORD PTR [rbp-0x20]
  96:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  9a:   0f 29 00                movaps XMMWORD PTR [rax],xmm0
Poznámka: zde jsou tedy vektorové operace přeloženy do velmi efektivního kódu.

5. Tytéž operace, ovšem prováděné s prvky se znaménkem

V této kapitole si ukážeme podobný příklad, jako byl ten z předchozí kapitoly, nyní ovšem s vektory, jejichž prvky jsou typu signed char/short/int/long:

#include <stdio.h>
 
typedef signed char v16sb __attribute__((vector_size(16)));
 
void add16ub(v16sb x, v16sb y, v16sb * z)
{
    *z = x + y;
}
 
typedef signed short v16ss __attribute__((vector_size(16)));
 
void add16us(v16ss x, v16ss y, v16ss * z)
{
    *z = x + y;
}
 
typedef signed int v16si __attribute__((vector_size(16)));
 
void add16ui(v16si x, v16si y, v16si * z)
{
    *z = x + y;
}
 
typedef signed long int v16sl __attribute__((vector_size(16)));
 
void add16ul(v16sl x, v16sl y, v16sl * z)
{
    *z = x + y;
}
 
int main(void)
{
    {
        v16sb x = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
        v16sb y =
            { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        };
        v16sb z;
 
        add16ub(x, y,  z);
 
        int i;
 
        puts("vector of signed chars");
 
        for (i = 0; i < sizeof(v16sb) / sizeof(signed char); i++) {
            printf("%d %d\n", i, z[i]);
        }
    }
 
    putchar('\n');
 
    {
        v16ss x = { 0, 1, 2, 3, 4, 5, 6, 7 };
        v16ss y = { 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
            0xffff
        };
        v16ss z;
 
        add16us(x, y,  z);
 
        int i;
 
        puts("vector of signed short ints");
 
        for (i = 0; i < sizeof(v16ss) / sizeof(signed short); i++) {
            printf("%d %d\n", i, z[i]);
        }
    }
 
    putchar('\n');
 
    {
        v16si x = { 0, 1, 2, 3 };
        v16si y = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff };
        v16si z;
 
        add16ui(x, y,  z);
 
        int i;
 
        puts("vector of signed ints");
 
        for (i = 0; i < sizeof(v16si) / sizeof(signed int); i++) {
            printf("%d %d\n", i, z[i]);
        }
    }
 
    putchar('\n');
 
    {
        v16sl x = { 0, 1 };
        v16sl y = { 0xffffffffffffffff, 0xffffffffffffffff };
        v16sl z;
 
        add16ul(x, y,  z);
 
        int i;
 
        puts("vector of signed longs");
 
        for (i = 0; i < sizeof(v16sl) / sizeof(signed long); i++) {
            printf("%d %ld\n", i, z[i]);
        }
    }
 
    return 0;
}

Výsledky všech čtyř vektorových operací vypadají následovně:

vector of signed chars
0 -1
1 0
2 1
3 2
4 3
5 4
6 5
7 6
8 7
9 8
10 9
11 10
12 11
13 12
14 13
15 14
 
vector of signed short ints
0 -1
1 0
2 1
3 2
4 3
5 4
6 5
7 6
 
vector of signed ints
0 -1
1 0
2 1
3 2
 
vector of signed longs
0 -1
1 0

6. Způsob překladu vektorových operací do assembleru

Vytištěné výsledky jsou sice značně odlišné (ostatně podle očekávání), ale jak se odlišuje sekvence instrukcí získaná přeložením těchto příkladů.

„Nevektorová varianta“:

    *z = x + y;
   c:   48 8b 45 10             mov    rax,QWORD PTR [rbp+0x10]
  10:   48 8b 55 20             mov    rdx,QWORD PTR [rbp+0x20]
  14:   48 89 c6                mov    rsi,rax
  17:   48 31 d6                xor    rsi,rdx
  1a:   48 b9 7f 7f 7f 7f 7f    movabs rcx,0x7f7f7f7f7f7f7f7f
  21:   7f 7f 7f
  24:   48 21 d1                and    rcx,rdx
  27:   48 ba 7f 7f 7f 7f 7f    movabs rdx,0x7f7f7f7f7f7f7f7f
  2e:   7f 7f 7f
  31:   48 21 d0                and    rax,rdx
  34:   48 ba 80 80 80 80 80    movabs rdx,0x8080808080808080
  3b:   80 80 80
  3e:   48 21 f2                and    rdx,rsi
  41:   48 01 c8                add    rax,rcx
  44:   48 89 d6                mov    rsi,rdx
  47:   48 31 c6                xor    rsi,rax
  4a:   48 8b 45 18             mov    rax,QWORD PTR [rbp+0x18]
  4e:   48 8b 55 28             mov    rdx,QWORD PTR [rbp+0x28]
  52:   48 89 c7                mov    rdi,rax
  55:   48 31 d7                xor    rdi,rdx
  58:   48 b9 7f 7f 7f 7f 7f    movabs rcx,0x7f7f7f7f7f7f7f7f
  5f:   7f 7f 7f
  62:   48 21 d1                and    rcx,rdx
  65:   48 ba 7f 7f 7f 7f 7f    movabs rdx,0x7f7f7f7f7f7f7f7f
  6c:   7f 7f 7f
  6f:   48 21 d0                and    rax,rdx
  72:   48 ba 80 80 80 80 80    movabs rdx,0x8080808080808080
  79:   80 80 80
  7c:   48 21 fa                and    rdx,rdi
  7f:   48 01 c8                add    rax,rcx
  82:   48 89 d1                mov    rcx,rdx
  85:   48 31 c1                xor    rcx,rax
  88:   b8 00 00 00 00          mov    eax,0x0
  8d:   ba 00 00 00 00          mov    edx,0x0
  92:   48 89 f0                mov    rax,rsi
  95:   48 89 ca                mov    rdx,rcx
  98:   48 8b 4d f8             mov    rcx,QWORD PTR [rbp-0x8]
  9c:   48 89 01                mov    QWORD PTR [rcx],rax
  9f:   48 89 51 08             mov    QWORD PTR [rcx+0x8],rdx

Součet vektorů s prvky typu signed char:

   8:   0f 29 45 f0             movaps XMMWORD PTR [rbp-0x10],xmm0
   c:   0f 29 4d e0             movaps XMMWORD PTR [rbp-0x20],xmm1
  10:   48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
    *z = x + y;
  14:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  19:   66 0f fc 45 e0          paddb  xmm0,XMMWORD PTR [rbp-0x20]
  1e:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  22:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Součet vektorů s prvky typu signed short:

  30:   0f 29 45 f0             movaps XMMWORD PTR [rbp-0x10],xmm0
  34:   0f 29 4d e0             movaps XMMWORD PTR [rbp-0x20],xmm1
  38:   48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
    *z = x + y;
  3c:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  41:   66 0f fd 45 e0          paddw  xmm0,XMMWORD PTR [rbp-0x20]
  46:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  4a:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Součet vektorů s prvky typu signed int:

  58:   0f 29 45 f0             movaps XMMWORD PTR [rbp-0x10],xmm0
  5c:   0f 29 4d e0             movaps XMMWORD PTR [rbp-0x20],xmm1
  60:   48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
    *z = x + y;
  64:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  69:   66 0f fe 45 e0          paddd  xmm0,XMMWORD PTR [rbp-0x20]
  6e:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  72:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

A konečně součet vektorů s prvky typu signed long:

  80:   0f 29 45 f0             movaps XMMWORD PTR [rbp-0x10],xmm0
  84:   0f 29 4d e0             movaps XMMWORD PTR [rbp-0x20],xmm1
  88:   48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
    *z = x + y;
  8c:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  91:   66 0f d4 45 e0          paddq  xmm0,XMMWORD PTR [rbp-0x20]
  96:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  9a:   0f 29 00                movaps XMMWORD PTR [rax],xmm0
Poznámka: na tomto příkladu je patrné, že se výsledné sekvence instrukcí nijak neliší.

7. Vektorové operace nad vektory s prvky typu floatdouble

Zbývá nám zjistit, jakým způsobem se přeloží kód s vektory, jejichž prvky jsou typu float a double:

#include <stdio.h>
 
typedef float v16float __attribute__((vector_size(16)));
 
void add16float(v16float x, v16float y, v16float * z)
{
    *z = x + y;
}
 
typedef double v16double __attribute__((vector_size(16)));
 
void add16double(v16double x, v16double y, v16double * z)
{
    *z = x + y;
}
 
int main(void)
{
    {
        v16float x = { 0, 1, 2, 3 };
        v16float y = { 0.1, 0.1, 0.1, 0.1 };
        v16float z;
 
        add16float(x, y,  z);
 
        int i;
 
        puts("vector of floats");
 
        for (i = 0; i < sizeof(v16float) / sizeof(float); i++) {
            printf("%d %f\n", i, z[i]);
        }
    }
 
    putchar('\n');
 
    {
        v16double x = { 0, 1 };
        v16double y = { 0.1, 0.1 };
        v16double z;
 
        add16double(x, y,  z);
 
        int i;
 
        puts("vector of doubles");
 
        for (i = 0; i < sizeof(v16double) / sizeof(double); i++) {
            printf("%d %f\n", i, z[i]);
        }
    }
    return 0;
}

Výsledky získané po překladu a spuštění tohoto jednoduchého demonstračního příkladu:

vector of floats
0 0.100000
1 1.100000
2 2.100000
3 3.100000
 
vector of doubles
0 0.100000
1 1.100000

8. Překlad do assembleru

Pokud jsou SIMD instrukce při překladu zakázány, jsou vektory sčítány prvek po prvku, zde konkrétně s využitím instrukce matematického koprocesoru FADDP. Pro původní prvky typu float se musí provést čtyři součty:

    *z = x + y;
   c:   d9 45 10                fld    DWORD PTR [rbp+0x10]
   f:   d9 45 20                fld    DWORD PTR [rbp+0x20]
  12:   de c1                   faddp  st(1),st
  14:   d9 45 14                fld    DWORD PTR [rbp+0x14]
  17:   d9 45 24                fld    DWORD PTR [rbp+0x24]
  1a:   de c1                   faddp  st(1),st
  1c:   d9 45 18                fld    DWORD PTR [rbp+0x18]
  1f:   d9 45 28                fld    DWORD PTR [rbp+0x28]
  22:   de c1                   faddp  st(1),st
  24:   d9 45 1c                fld    DWORD PTR [rbp+0x1c]
  27:   d9 45 2c                fld    DWORD PTR [rbp+0x2c]
  2a:   de c1                   faddp  st(1),st
  2c:   d9 cb                   fxch   st(3)
  2e:   d9 5d d0                fstp   DWORD PTR [rbp-0x30]
  31:   d9 c9                   fxch   st(1)
  33:   d9 5d d4                fstp   DWORD PTR [rbp-0x2c]
  36:   d9 5d d8                fstp   DWORD PTR [rbp-0x28]
  39:   d9 5d dc                fstp   DWORD PTR [rbp-0x24]
  3c:   48 8b 45 d0             mov    rax,QWORD PTR [rbp-0x30]
  40:   48 89 45 f0             mov    QWORD PTR [rbp-0x10],rax
  44:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  48:   48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
  4c:   48 8b 45 e8             mov    rax,QWORD PTR [rbp-0x18]
  50:   48 8b 55 f0             mov    rdx,QWORD PTR [rbp-0x10]
  54:   48 89 10                mov    QWORD PTR [rax],rdx
  57:   48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
  5b:   48 89 50 08             mov    QWORD PTR [rax+0x8],rdx

Pro prvky vektorů typu double se použijí pouze dvě operace součtu, protože celková délka vektoru je 2×sizeof(double)=16 bajtů:

  6e:   dd 45 10                fld    QWORD PTR [rbp+0x10]
  71:   dd 45 20                fld    QWORD PTR [rbp+0x20]
  74:   de c1                   faddp  st(1),st
  76:   dd 45 18                fld    QWORD PTR [rbp+0x18]
  79:   dd 45 28                fld    QWORD PTR [rbp+0x28]
  7c:   de c1                   faddp  st(1),st
  7e:   d9 c9                   fxch   st(1)
  80:   dd 5d d0                fstp   QWORD PTR [rbp-0x30]
  83:   dd 5d d8                fstp   QWORD PTR [rbp-0x28]
  86:   48 8b 45 d0             mov    rax,QWORD PTR [rbp-0x30]
  8a:   48 89 45 f0             mov    QWORD PTR [rbp-0x10],rax
  8e:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  92:   48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
  96:   48 8b 45 e8             mov    rax,QWORD PTR [rbp-0x18]
  9a:   48 8b 55 f0             mov    rdx,QWORD PTR [rbp-0x10]
  9e:   48 89 10                mov    QWORD PTR [rax],rdx
  a1:   48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
  a5:   48 89 50 08             mov    QWORD PTR [rax+0x8],rdx

Pro nás je dnes pochopitelně mnohem zajímavější překlad využívající SIMD instrukce. Součet prvků vektoru typu float je realizován takto – instrukcí ADDPS (viz též úvodní článek):

    *z = x + y;
  14:   0f 28 45 f0             movaps xmm0,XMMWORD PTR [rbp-0x10]
  18:   0f 58 45 e0             addps  xmm0,XMMWORD PTR [rbp-0x20]
  1c:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  20:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

A pro vektory s prvky typu double vypadá jejich součet následovně – je realizován instrukcí ADDPD:

  3a:   66 0f 28 45 f0          movapd xmm0,XMMWORD PTR [rbp-0x10]
  3f:   66 0f 58 45 e0          addpd  xmm0,XMMWORD PTR [rbp-0x20]
  44:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  48:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

9. Vektor není libovolně velké pole – omezení GCC

Z předchozího textu by se mohlo zdát, že vektory zavedené v rámci rozšíření GCC jsou vlastně určitou formou polí a že je možné nebo vhodné nahradit pole (předem známé délky) právě za vektory, bez ohledu na jejich velikost. Teoreticky to samozřejmě možné je, ale výsledná sekvence instrukcí vygenerovaná překladačem nás může nepříjemně překvapit, zejména ve chvíli, kdy se například pokusíme vytvořit vektor se všemi pixely obrázku atd.

Vyzkoušejme si to na příkladu, v němž je použit vektor s délkou 1024 bajtů (což je vlastně s ohledem na další možné varianty ještě velmi krátký vektor):

typedef float v1024f __attribute__((vector_size(1024)));
 
void addVectors(v1024f * x, v1024f * y, v1024f * z)
{
    *z = *x + *y;
}
 
int main(void)
{
    v1024f x = { 1.0 };
    v1024f y = { 1.0 };
    v1024f z;
 
    addVectors( x,  y,  z);
 
    return 0;
}
Poznámka: povšimněte si, že zdrojové vektory předáváme přes ukazatel a nikoli hodnotou – to by v žádném případě nebylo efektivní. Navíc namísto cílové hodnoty opět používáme předávání přes ukazatel, a to ze stejného důvodu (navíc by si překladač stěžoval na problém vrácení zarovnané 1024 bajtové hodnoty).

10. Způsob překladu operace s dlouhým vektorem do assembleru

Nyní se můžeme podívat na výsledek překladu předchozího demonstračního příkladu v případě, že je překlad proveden s vypnutím SIMD instrukcí (zde konkrétně se použil přepínač -mno-sse). Přeložená funkce je v tomto případě obrovská, protože strojový kód přesahuje pět kilobajtů. Nejprve můžeme vidět dva blokové přesuny dat, přičemž každý blok má velikost 1024 bajtů (přesouvá se 128×8 bajtů). Tyto přesuny jsou realizovány instrukcí movs s prefixem rep:

      3a:       48 8b 94 24 80 0b 00    mov    rdx,QWORD PTR [rsp+0xb80]
      41:       00
      42:       48 8d 84 24 88 0b 00    lea    rax,[rsp+0xb88]
      49:       00
      4a:       48 89 d6                mov    rsi,rdx
      4d:       ba 80 00 00 00          mov    edx,0x80
      52:       48 89 c7                mov    rdi,rax
      55:       48 89 d1                mov    rcx,rdx
      58:       f3 48 a5                rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
      5b:       48 8b 94 24 78 0b 00    mov    rdx,QWORD PTR [rsp+0xb78]
      62:       00
      63:       48 8d 84 24 88 0f 00    lea    rax,[rsp+0xf88]
      6a:       00
      6b:       48 89 d6                mov    rsi,rdx
      6e:       ba 80 00 00 00          mov    edx,0x80
      73:       48 89 c7                mov    rdi,rax
      76:       48 89 d1                mov    rcx,rdx
      79:       f3 48 a5                rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]

Dále můžeme v kódu vidět opakující se sekvenci čtveřice instrukcí fld+fld+faddp+fstp, což znamená, že se na zásobník ST uloží dvojice operandů typu float, ty se sečtou s uložením výsledku zpět na zásobník a nakonec se výsledek uloží instrukcí fstp (písmeno „p“ na konci znamená „pop“ a nikoli „parallel“, jako je tomu v SSE):

      7c:       d9 84 24 88 0b 00 00    fld    DWORD PTR [rsp+0xb88]
      83:       d9 84 24 88 0f 00 00    fld    DWORD PTR [rsp+0xf88]
      8a:       de c1                   faddp  st(1),st
      8c:       d9 9c 24 64 07 00 00    fstp   DWORD PTR [rsp+0x764]
      93:       d9 84 24 8c 0b 00 00    fld    DWORD PTR [rsp+0xb8c]
      9a:       d9 84 24 8c 0f 00 00    fld    DWORD PTR [rsp+0xf8c]
      a1:       de c1                   faddp  st(1),st
      a3:       d9 9c 24 60 07 00 00    fstp   DWORD PTR [rsp+0x760]
      aa:       d9 84 24 90 0b 00 00    fld    DWORD PTR [rsp+0xb90]
      b1:       d9 84 24 90 0f 00 00    fld    DWORD PTR [rsp+0xf90]
      b8:       de c1                   faddp  st(1),st
      ba:       d9 9c 24 5c 07 00 00    fstp   DWORD PTR [rsp+0x75c]

Tato sekvence se opakuje 256× (!), což přesně odpovídá počtu prvků vektoru. Na konci je sekvence instrukcí poněkud odlišná, neboť výsledky zůstávají na zásobníku (pro osm prvků):

    171b:       d9 84 24 7c 0f 00 00    fld    DWORD PTR [rsp+0xf7c]
    1722:       d9 84 24 7c 13 00 00    fld    DWORD PTR [rsp+0x137c]
    1729:       de c1                   faddp  st(1),st
    172b:       d9 84 24 80 0f 00 00    fld    DWORD PTR [rsp+0xf80]
    1732:       d9 84 24 80 13 00 00    fld    DWORD PTR [rsp+0x1380]
    1739:       de c1                   faddp  st(1),st
    173b:       d9 84 24 84 0f 00 00    fld    DWORD PTR [rsp+0xf84]
    1742:       d9 84 24 84 13 00 00    fld    DWORD PTR [rsp+0x1384]
    1749:       de c1                   faddp  st(1),st
    174b:       d9 84 24 64 07 00 00    fld    DWORD PTR [rsp+0x764]
    1752:       d9 9c 24 68 07 00 00    fstp   DWORD PTR [rsp+0x768]
Poznámka: podobný kód, pokud by se měl často opakovat v dalších variantách, by mohl vést k tomu, že by se zbytečně zaplňovala L1 cache mikroprocesoru.

Při zapnutí optimalizací dostaneme nepatrně lepší kód, v němž se na zásobník ukládá pouze jeden z operandů. Ono opakování výpočtu 256× je zde stále použito, stejně jako oba blokové přesuny na začátku:

      4f:       d9 84 24 88 03 00 00    fld    DWORD PTR [rsp+0x388]
      56:       d8 84 24 88 07 00 00    fadd   DWORD PTR [rsp+0x788]
      5d:       d9 5c 24 88             fstp   DWORD PTR [rsp-0x78]
      61:       d9 84 24 8c 03 00 00    fld    DWORD PTR [rsp+0x38c]
      68:       d8 84 24 8c 07 00 00    fadd   DWORD PTR [rsp+0x78c]
      6f:       d9 5c 24 8c             fstp   DWORD PTR [rsp-0x74]

až:

    147f:       d9 84 24 84 07 00 00    fld    DWORD PTR [rsp+0x784]
    1486:       d8 84 24 84 0b 00 00    fadd   DWORD PTR [rsp+0xb84]
    148d:       d9 9c 24 84 03 00 00    fstp   DWORD PTR [rsp+0x384]

Mohlo by se zdát, že po povolení SIMD operací (zde to bude SSE) se situace značně zlepší. Ovšem i zde můžeme na začátku vidět dvojici blokových přenosů o celkové velikosti dvou kilobajtů:

  3a:   48 8b 94 24 80 07 00    mov    rdx,QWORD PTR [rsp+0x780]
  41:   00
  42:   48 8d 84 24 88 07 00    lea    rax,[rsp+0x788]
  49:   00
  4a:   48 89 d6                mov    rsi,rdx
  4d:   ba 80 00 00 00          mov    edx,0x80
  52:   48 89 c7                mov    rdi,rax
  55:   48 89 d1                mov    rcx,rdx
  58:   f3 48 a5                rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
  5b:   48 8b 94 24 78 07 00    mov    rdx,QWORD PTR [rsp+0x778]
  62:   00
  63:   48 8d 84 24 88 0b 00    lea    rax,[rsp+0xb88]
  6a:   00
  6b:   48 89 d6                mov    rsi,rdx
  6e:   ba 80 00 00 00          mov    edx,0x80
  73:   48 89 c7                mov    rdi,rax
  76:   48 89 d1                mov    rcx,rdx
  79:   f3 48 a5                rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]

Samotný výpočet je realizován čtveřicí instrukcí movaps+movaps+addps+movaps:

  7c:   0f 28 8c 24 88 07 00    movaps xmm1,XMMWORD PTR [rsp+0x788]
  83:   00
  84:   0f 28 84 24 88 0b 00    movaps xmm0,XMMWORD PTR [rsp+0xb88]
  8b:   00
  8c:   0f 58 c8                addps  xmm1,xmm0
  8f:   0f 29 8c 24 58 03 00    movaps XMMWORD PTR [rsp+0x358],xmm1

Jedná se o opakující se sekvenci instrukcí, která končí takto:

 78e:   0f 28 8c 24 58 0b 00    movaps xmm1,XMMWORD PTR [rsp+0xb58]
 795:   00
 796:   0f 28 84 24 58 0f 00    movaps xmm0,XMMWORD PTR [rsp+0xf58]
 79d:   00
 79e:   0f 28 d9                movaps xmm3,xmm1
 7a1:   0f 58 d8                addps  xmm3,xmm0
 7a4:   0f 28 8c 24 68 0b 00    movaps xmm1,XMMWORD PTR [rsp+0xb68]
 7ab:   00
 7ac:   0f 28 84 24 68 0f 00    movaps xmm0,XMMWORD PTR [rsp+0xf68]
Poznámka: tato sekvence se opakuje 64× a nikoli 256×, protože výpočet je proveden vždy se čtyřmi prvky vektoru paralelně. Takže se jedná o určité zlepšení.

Po optimalizaci získáme kratší kód. Celý výpočet začíná následovně:

  4f:   0f 28 84 24 88 03 00    movaps xmm0,XMMWORD PTR [rsp+0x388]
  56:   00
  57:   0f 58 84 24 88 07 00    addps  xmm0,XMMWORD PTR [rsp+0x788]
  5e:   00
  5f:   0f 29 44 24 88          movaps XMMWORD PTR [rsp-0x78],xmm0

  64:   0f 28 84 24 98 03 00    movaps xmm0,XMMWORD PTR [rsp+0x398]
  6b:   00
  6c:   0f 58 84 24 98 07 00    addps  xmm0,XMMWORD PTR [rsp+0x798]
  73:   00
  74:   0f 29 44 24 98          movaps XMMWORD PTR [rsp-0x68],xmm0

a končí takto:

 607:   0f 28 84 24 78 07 00    movaps xmm0,XMMWORD PTR [rsp+0x778]
 60e:   00
 60f:   0f 58 84 24 78 0b 00    addps  xmm0,XMMWORD PTR [rsp+0xb78]
 616:   00
 617:   0f 29 84 24 78 03 00    movaps XMMWORD PTR [rsp+0x378],xmm0
Poznámka: mohlo by se zdát, že se vlastně jedná o brutálně provedené rozbalení smyčky, ovšem zde na úkor kompaktnosti kódu, který v důsledku musí být uložen na disku, načten do paměti a spuštěn z L1 cache. Mimochodem se můžeme podívat na to, jak kód roste prakticky nade všechny meze:
typedef float v1024f __attribute__((vector_size(65536)));
 
void addVectors(v1024f * x, v1024f * y, v1024f * z)
{
    *z = *x + *y;
}
 
int main(void)
{
    v1024f x = { 1.0 };
    v1024f y = { 1.0 };
    v1024f z;
 
    addVectors(&x, &y, &z);
 
    return 0;
}

Výsledkem překladu bude tento absurdně velký objektový kód:

$ ls -lh simd16_1.o
 
-rw-rw-r-- 1 ptisnovs ptisnovs 193K Oct  9 09:23 simd16B.o
Poznámka: nehledě na to, že překlad bude velmi pomalý – desítky sekund.

11. Ne všechny operace mohou být v SIMD podporovány

Rozšíření GCC umožňuje provádět následující operace s prvky celočíselných vektorů (předpokládá se použití dvojice vektorů stejné délky):

+, -, *, /, unární minus, ^, |, &, ~, %, << a >>

Pokud by se jednalo o prvky typu float nebo double, jsou povoleny tyto operace:

+, -, *, / a unární minus

Ovšem samotné rozšíření GCC žádným způsobem nespecifikuje, jakým způsobem se vlastně „vektorové operace“ přeloží do výsledného strojového kódu. Můžeme si to otestovat na následujícím demonstračním příkladu, v němž jsou použity všechny podporované binární operace pro celočíselné prvky vektorů:

#include <stdio.h>
 
typedef signed char v16ib __attribute__((vector_size(16)));
 
void add16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x + y;
}
 
void sub16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x - y;
}
 
void mul16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x * y;
}
 
void div16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x / y;
}
 
void mod16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x % y;
}
 
void and16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x & y;
}
 
void or16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x | y;
}
 
void xor16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x ^ y;
}
 
void rshift16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x >> y;
}
 
void lshift16ib(v16ib x, v16ib y, v16ib * z)
{
    *z = x << y;
}
 
void print_vectors(const char *message, const char *op, v16ib *x,v16ib  *y,v16ib  *z)
{
    int i;
 
    puts(message);
    for (i = 0; i < sizeof(v16ib) / sizeof(signed char); i++) {
        printf("%2d    %d %s %d = %d\n", i, (*x)[i], op, (*y)[i], (*z)[i]);
    }
 
    putchar('\n');
}
 
int main(void)
{
    v16ib x;
    v16ib y;
    v16ib z;
    int i;
 
    for (i = 0; i < sizeof(v16ib) / sizeof(signed char); i++) {
        x[i] = i*2;
        y[i] = 16-i;
    }
 
    add16ib(x, y, &z);
    print_vectors("vector addition", "+", &x, &y, &z);
 
    sub16ib(x, y, &z);
    print_vectors("vector subtraction", "-", &x, &y, &z);
 
    mul16ib(x, y, &z);
    print_vectors("vector multiply", "*", &x, &y, &z);
 
    div16ib(x, y, &z);
    print_vectors("vector divide", "/", &x, &y, &z);
 
    mod16ib(x, y, &z);
    print_vectors("vector modulo", "%", &x, &y, &z);
 
    and16ib(x, y, &z);
    print_vectors("vector bitwise and", "&", &x, &y, &z);
 
    or16ib(x, y, &z);
    print_vectors("vector bitwise or", "|", &x, &y, &z);
 
    xor16ib(x, y, &z);
    print_vectors("vector bitwise xor", "^", &x, &y, &z);
 
    rshift16ib(x, y, &z);
    print_vectors("vector right shift", ">>", &x, &y, &z);
 
    lshift16ib(x, y, &z);
    print_vectors("vector left shift", "<<", &x, &y, &z);
 
    return 0;
}

S těmito výsledky:

vector addition
 0    0 + 16 = 16
 1    2 + 15 = 17
 2    4 + 14 = 18
 3    6 + 13 = 19
 4    8 + 12 = 20
 5    10 + 11 = 21
 6    12 + 10 = 22
 7    14 + 9 = 23
 8    16 + 8 = 24
 9    18 + 7 = 25
10    20 + 6 = 26
11    22 + 5 = 27
12    24 + 4 = 28
13    26 + 3 = 29
14    28 + 2 = 30
15    30 + 1 = 31
 
vector subtraction
 0    0 - 16 = -16
 1    2 - 15 = -13
 2    4 - 14 = -10
 3    6 - 13 = -7
 4    8 - 12 = -4
 5    10 - 11 = -1
 6    12 - 10 = 2
 7    14 - 9 = 5
 8    16 - 8 = 8
 9    18 - 7 = 11
10    20 - 6 = 14
11    22 - 5 = 17
12    24 - 4 = 20
13    26 - 3 = 23
14    28 - 2 = 26
15    30 - 1 = 29
 
vector multiply
 0    0 * 16 = 0
 1    2 * 15 = 30
 2    4 * 14 = 56
 3    6 * 13 = 78
 4    8 * 12 = 96
 5    10 * 11 = 110
 6    12 * 10 = 120
 7    14 * 9 = 126
 8    16 * 8 = -128
 9    18 * 7 = 126
10    20 * 6 = 120
11    22 * 5 = 110
12    24 * 4 = 96
13    26 * 3 = 78
14    28 * 2 = 56
15    30 * 1 = 30
 
vector divide
 0    0 / 16 = 0
 1    2 / 15 = 0
 2    4 / 14 = 0
 3    6 / 13 = 0
 4    8 / 12 = 0
 5    10 / 11 = 0
 6    12 / 10 = 1
 7    14 / 9 = 1
 8    16 / 8 = 2
 9    18 / 7 = 2
10    20 / 6 = 3
11    22 / 5 = 4
12    24 / 4 = 6
13    26 / 3 = 8
14    28 / 2 = 14
15    30 / 1 = 30
 
vector modulo
 0    0 % 16 = 0
 1    2 % 15 = 2
 2    4 % 14 = 4
 3    6 % 13 = 6
 4    8 % 12 = 8
 5    10 % 11 = 10
 6    12 % 10 = 2
 7    14 % 9 = 5
 8    16 % 8 = 0
 9    18 % 7 = 4
10    20 % 6 = 2
11    22 % 5 = 2
12    24 % 4 = 0
13    26 % 3 = 2
14    28 % 2 = 0
15    30 % 1 = 0
 
vector bitwise and
 0    0 & 16 = 0
 1    2 & 15 = 2
 2    4 & 14 = 4
 3    6 & 13 = 4
 4    8 & 12 = 8
 5    10 & 11 = 10
 6    12 & 10 = 8
 7    14 & 9 = 8
 8    16 & 8 = 0
 9    18 & 7 = 2
10    20 & 6 = 4
11    22 & 5 = 4
12    24 & 4 = 0
13    26 & 3 = 2
14    28 & 2 = 0
15    30 & 1 = 0
 
vector bitwise or
 0    0 | 16 = 16
 1    2 | 15 = 15
 2    4 | 14 = 14
 3    6 | 13 = 15
 4    8 | 12 = 12
 5    10 | 11 = 11
 6    12 | 10 = 14
 7    14 | 9 = 15
 8    16 | 8 = 24
 9    18 | 7 = 23
10    20 | 6 = 22
11    22 | 5 = 23
12    24 | 4 = 28
13    26 | 3 = 27
14    28 | 2 = 30
15    30 | 1 = 31
 
vector bitwise xor
 0    0 ^ 16 = 16
 1    2 ^ 15 = 13
 2    4 ^ 14 = 10
 3    6 ^ 13 = 11
 4    8 ^ 12 = 4
 5    10 ^ 11 = 1
 6    12 ^ 10 = 6
 7    14 ^ 9 = 7
 8    16 ^ 8 = 24
 9    18 ^ 7 = 21
10    20 ^ 6 = 18
11    22 ^ 5 = 19
12    24 ^ 4 = 28
13    26 ^ 3 = 25
14    28 ^ 2 = 30
15    30 ^ 1 = 31
 
vector right shift
 0    0 >> 16 = 0
 1    2 >> 15 = 0
 2    4 >> 14 = 0
 3    6 >> 13 = 0
 4    8 >> 12 = 0
 5    10 >> 11 = 0
 6    12 >> 10 = 0
 7    14 >> 9 = 0
 8    16 >> 8 = 0
 9    18 >> 7 = 0
10    20 >> 6 = 0
11    22 >> 5 = 0
12    24 >> 4 = 1
13    26 >> 3 = 3
14    28 >> 2 = 7
15    30 >> 1 = 15
 
vector left shift
 0    0 << 16 = 0
 1    2 << 15 = 0
 2    4 << 14 = 0
 3    6 << 13 = 0
 4    8 << 12 = 0
 5    10 << 11 = 0
 6    12 << 10 = 0
 7    14 << 9 = 0
 8    16 << 8 = 0
 9    18 << 7 = 0
10    20 << 6 = 0
11    22 << 5 = -64
12    24 << 4 = -128
13    26 << 3 = -48
14    28 << 2 = 112
15    30 << 1 = 60

12. Výsledek překladu předchozího programu do kódu se SIMD operacemi

Podívejme se nyní postupně na způsob překladu předchozího programu ve chvíli, kdy jsou při překladu povoleny SIMD operace. Pro stručnost si uvedeme jen „výpočetní část“ každé z funkcí, tedy bez přípravy operandů a bez úklidu zásobníku na konci každé funkce.

Vektorový součet je realizován jedinou instrukcí PADDB:

  14:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  19:   66 0f fc 45 e0          paddb  xmm0,XMMWORD PTR [rbp-0x20]
  1e:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  22:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Vektorový rozdíl je realizován jedinou instrukcí PSUBB:

  3c:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  41:   66 0f f8 45 e0          psubb  xmm0,XMMWORD PTR [rbp-0x20]
  46:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  4a:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Součin prvků vektorů již není tak triviální, jak by se možná mohlo na první pohled zdát, neboť je nutné zajistit, aby každý výsledný prvek měl opět pouze osm bitů výsledku a nikoli šestnáct bitů. Nejdříve se prvky vektorů instrukcemi PUNPCKLBW a PUNPCKHBW rozdělí na vektory šestnáctibitových slov, provede se výpočet instrukcí PMULLW (tedy skutečně násobení šestnáctibitových hodnot) a poté se výsledky opět „zkomprimují“ instrukcí PACKUSWB

  64:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
  69:   66 0f 6f 4d e0          movdqa xmm1,XMMWORD PTR [rbp-0x20]
  6e:   66 0f 6f d9             movdqa xmm3,xmm1
  72:   66 0f 60 d9             punpcklbw xmm3,xmm1
  76:   66 0f 6f d1             movdqa xmm2,xmm1
  7a:   66 0f 68 d1             punpckhbw xmm2,xmm1
  7e:   66 0f 6f c8             movdqa xmm1,xmm0
  82:   66 0f 60 c8             punpcklbw xmm1,xmm0
  86:   66 0f 68 c0             punpckhbw xmm0,xmm0
  8a:   66 0f 6f e1             movdqa xmm4,xmm1
  8e:   66 0f 6f cb             movdqa xmm1,xmm3
  92:   66 0f d5 cc             pmullw xmm1,xmm4
  96:   66 0f 6f d8             movdqa xmm3,xmm0
  9a:   66 0f 6f c2             movdqa xmm0,xmm2
  9e:   66 0f 6f d3             movdqa xmm2,xmm3
  a2:   66 0f d5 d0             pmullw xmm2,xmm0
  a6:   66 0f 6f 05 00 00 00    movdqa xmm0,XMMWORD PTR [rip+0x0]
  ad:   00
  ae:   66 0f db c8             pand   xmm1,xmm0
  b2:   66 0f db c2             pand   xmm0,xmm2
  b6:   66 0f 6f e9             movdqa xmm5,xmm1
  ba:   66 0f 67 e8             packuswb xmm5,xmm0
  be:   66 0f 6f c5             movdqa xmm0,xmm5
  c2:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  c6:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Další operací je podíl prvků vektorů, který je složitý a pomalý, protože je realizován sekvenčně, konkrétně standardní instrukcí IDIV (!), předtím je ještě nutné operand převést z bajtu na 16bitové slovo instrukcí CBW (uff):

  e9:   0f b6 45 c0             movzx  eax,BYTE PTR [rbp-0x40]
  ed:   0f b6 55 b0             movzx  edx,BYTE PTR [rbp-0x50]
  f1:   66 98                   cbw
  f3:   f6 fa                   idiv   dl
  f5:   41 89 c7                mov    r15d,eax
  f8:   0f b6 45 c1             movzx  eax,BYTE PTR [rbp-0x3f]
  fc:   0f b6 55 b1             movzx  edx,BYTE PTR [rbp-0x4f]
 100:   66 98                   cbw
 102:   f6 fa                   idiv   dl
 104:   88 45 90                mov    BYTE PTR [rbp-0x70],al
 107:   0f b6 45 c2             movzx  eax,BYTE PTR [rbp-0x3e]
 10b:   0f b6 55 b2             movzx  edx,BYTE PTR [rbp-0x4e]
 10f:   66 98                   cbw
 111:   f6 fa                   idiv   dl
 113:   88 45 a7                mov    BYTE PTR [rbp-0x59],al
 116:   0f b6 45 c3             movzx  eax,BYTE PTR [rbp-0x3d]
 11a:   0f b6 55 b3             movzx  edx,BYTE PTR [rbp-0x4d]
 11e:   66 98                   cbw
 120:   f6 fa                   idiv   dl
 122:   88 45 a6                mov    BYTE PTR [rbp-0x5a],al
 125:   0f b6 45 c4             movzx  eax,BYTE PTR [rbp-0x3c]
 129:   0f b6 55 b4             movzx  edx,BYTE PTR [rbp-0x4c]
 12d:   66 98                   cbw
 12f:   f6 fa                   idiv   dl
 131:   88 45 a5                mov    BYTE PTR [rbp-0x5b],al
 134:   0f b6 45 c5             movzx  eax,BYTE PTR [rbp-0x3b]
 138:   0f b6 55 b5             movzx  edx,BYTE PTR [rbp-0x4b]
 13c:   66 98                   cbw
 13e:   f6 fa                   idiv   dl
 140:   41 89 c2                mov    r10d,eax
 143:   0f b6 45 c6             movzx  eax,BYTE PTR [rbp-0x3a]
 147:   0f b6 55 b6             movzx  edx,BYTE PTR [rbp-0x4a]
 14b:   66 98                   cbw
 14d:   f6 fa                   idiv   dl
 14f:   41 89 c0                mov    r8d,eax
 152:   0f b6 45 c7             movzx  eax,BYTE PTR [rbp-0x39]
 156:   0f b6 55 b7             movzx  edx,BYTE PTR [rbp-0x49]
 15a:   66 98                   cbw
 15c:   f6 fa                   idiv   dl
 15e:   89 c7                   mov    edi,eax
 160:   0f b6 45 c8             movzx  eax,BYTE PTR [rbp-0x38]
 164:   0f b6 55 b8             movzx  edx,BYTE PTR [rbp-0x48]
 168:   66 98                   cbw
 16a:   f6 fa                   idiv   dl
 16c:   41 89 c3                mov    r11d,eax
 16f:   0f b6 45 c9             movzx  eax,BYTE PTR [rbp-0x37]
 173:   0f b6 55 b9             movzx  edx,BYTE PTR [rbp-0x47]
 177:   66 98                   cbw
 179:   f6 fa                   idiv   dl
 17b:   89 c3                   mov    ebx,eax
 17d:   0f b6 45 ca             movzx  eax,BYTE PTR [rbp-0x36]
 181:   0f b6 55 ba             movzx  edx,BYTE PTR [rbp-0x46]
 185:   66 98                   cbw
 187:   f6 fa                   idiv   dl
 189:   41 89 c4                mov    r12d,eax
 18c:   0f b6 45 cb             movzx  eax,BYTE PTR [rbp-0x35]
 190:   0f b6 55 bb             movzx  edx,BYTE PTR [rbp-0x45]
 194:   66 98                   cbw
 196:   f6 fa                   idiv   dl
 198:   41 89 c5                mov    r13d,eax
 19b:   0f b6 45 cc             movzx  eax,BYTE PTR [rbp-0x34]
 19f:   0f b6 55 bc             movzx  edx,BYTE PTR [rbp-0x44]
 1a3:   66 98                   cbw
 1a5:   f6 fa                   idiv   dl
 1a7:   41 89 c6                mov    r14d,eax
 1aa:   0f b6 45 cd             movzx  eax,BYTE PTR [rbp-0x33]
 1ae:   0f b6 55 bd             movzx  edx,BYTE PTR [rbp-0x43]
 1b2:   66 98                   cbw
 1b4:   f6 fa                   idiv   dl
 1b6:   41 89 c1                mov    r9d,eax
 1b9:   0f b6 45 ce             movzx  eax,BYTE PTR [rbp-0x32]
 1bd:   0f b6 55 be             movzx  edx,BYTE PTR [rbp-0x42]
 1c1:   66 98                   cbw
 1c3:   f6 fa                   idiv   dl
 1c5:   89 c6                   mov    esi,eax
 1c7:   0f b6 45 cf             movzx  eax,BYTE PTR [rbp-0x31]
 1cb:   0f b6 55 bf             movzx  edx,BYTE PTR [rbp-0x41]
 1cf:   66 98                   cbw
 1d1:   f6 fa                   idiv   dl

Aplikace operátoru & na prvky vektoru s využitím instrukce PAND:

    *z = x & y;
 4a2:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
 4a7:   66 0f db 45 e0          pand   xmm0,XMMWORD PTR [rbp-0x20]
 4ac:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
 4b0:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Aplikace operátoru | na prvky vektoru s využitím instrukce POR:

    *z = x | y;
 4ca:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
 4cf:   66 0f eb 45 e0          por    xmm0,XMMWORD PTR [rbp-0x20]
 4d4:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
 4d8:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Aplikace operátoru ^ na prvky vektoru s využitím instrukce PXOR:

    *z = x ^ y;
 4f2:   66 0f 6f 45 f0          movdqa xmm0,XMMWORD PTR [rbp-0x10]
 4f7:   66 0f ef 45 e0          pxor   xmm0,XMMWORD PTR [rbp-0x20]
 4fc:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
 500:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Překlad funkce, v níž se provádí bitový posun prvků jednoho vektoru na základě hodnot uložených ve druhém vektoru, je stejně tristní, jako tomu bylo u dělení prvků – žádné vektorové (přesněji řečen SIMD) instrukce zde nejsou použity:

523:    0f b6 55 c0             movzx  edx,BYTE PTR [rbp-0x40]
 527:   0f b6 45 b0             movzx  eax,BYTE PTR [rbp-0x50]
 52b:   0f be c0                movsx  eax,al
 52e:   89 c1                   mov    ecx,eax
 530:   d2 fa                   sar    dl,cl
 ...
 ...
 ...
 634:   0f be c0                movsx  eax,al
 637:   89 d7                   mov    edi,edx
 639:   89 c1                   mov    ecx,eax
 63b:   40 d2 ff                sar    dil,cl
 63e:   0f b6 55 cf             movzx  edx,BYTE PTR [rbp-0x31]
 642:   0f b6 45 bf             movzx  eax,BYTE PTR [rbp-0x41]
Poznámka: prakticky stejně je tomu v případě posunu doleva.

13. Základní aritmetické operace s prvky vektorů typu float

Nyní se podívejme na to, jakým způsobem se s využitím SIMD operací přeloží následující demonstrační příklad, v němž jsou použity všechny „vektorové operátory“ pro vektory s prvky typu float, tedy s numerickými hodnotami uloženými v systému plovoucí řádové tečky/čárky (těchto operátorů je mnohem méně, v porovnání s celočíselnými prvky vektorů):

#include <stdio.h>
 
typedef float v16float __attribute__((vector_size(16)));
 
void add16float(v16float x, v16float y, v16float * z)
{
    *z = x + y;
}
 
void sub16float(v16float x, v16float y, v16float * z)
{
    *z = x - y;
}
 
void mul16float(v16float x, v16float y, v16float * z)
{
    *z = x * y;
}
 
void div16float(v16float x, v16float y, v16float * z)
{
    *z = x / y;
}
 
void print_vectors(const char *message, const char op, v16float * x,
                   v16float * y, v16float * z)
{
    int i;
 
    puts(message);
    for (i = 0; i < sizeof(v16float) / sizeof(float); i++) {
        printf("%2d    %5.3f %c %5.3f = %5.3f\n", i, (*x)[i], op, (*y)[i], (*z)[i]);
    }
 
    putchar('\n');
}
 
int main(void)
{
    v16float x;
    v16float y;
    v16float z;
    int i;
 
    for (i = 0; i < sizeof(v16float) / sizeof(float); i++) {
        x[i] = i;
        y[i] = i + 0.1;
    }
 
    add16float(x, y, &z);
    print_vectors("vector addition", '+', &x, &y, &z);
 
    sub16float(x, y, &z);
    print_vectors("vector subtraction", '-', &x, &y, &z);
 
    mul16float(x, y, &z);
    print_vectors("vector multiply", '*', &x, &y, &z);
 
    div16float(x, y, &z);
    print_vectors("vector divide", '/', &x, &y, &z);
 
    return 0;
}

S výsledky:

vector addition
 0    0.000 + 0.100 = 0.100
 1    1.000 + 1.100 = 2.100
 2    2.000 + 2.100 = 4.100
 3    3.000 + 3.100 = 6.100
 
vector subtraction
 0    0.000 - 0.100 = -0.100
 1    1.000 - 1.100 = -0.100
 2    2.000 - 2.100 = -0.100
 3    3.000 - 3.100 = -0.100
 
vector multiply
 0    0.000 * 0.100 = 0.000
 1    1.000 * 1.100 = 1.100
 2    2.000 * 2.100 = 4.200
 3    3.000 * 3.100 = 9.300
 
vector divide
 0    0.000 / 0.100 = 0.000
 1    1.000 / 1.100 = 0.909
 2    2.000 / 2.100 = 0.952
 3    3.000 / 3.100 = 0.968

14. Překlad bez použití instrukcí SIMD

Nejprve se podívejme na způsob překladu tohoto demonstračního příkladu ve chvíli, kdy nejsou povoleny SIMD instrukce (tedy v našem případě SSE). Neoptimalizovaný kód součtu dvou čtyřprvkových vektorů je (nepřekvapivě) založen na instrukci FADDP:

   c:   d9 45 10                fld    DWORD PTR [rbp+0x10]
   f:   d9 45 20                fld    DWORD PTR [rbp+0x20]
  12:   de c1                   faddp  st(1),st
  14:   d9 45 14                fld    DWORD PTR [rbp+0x14]
  17:   d9 45 24                fld    DWORD PTR [rbp+0x24]
  1a:   de c1                   faddp  st(1),st
  1c:   d9 45 18                fld    DWORD PTR [rbp+0x18]
  1f:   d9 45 28                fld    DWORD PTR [rbp+0x28]
  22:   de c1                   faddp  st(1),st
  24:   d9 45 1c                fld    DWORD PTR [rbp+0x1c]
  27:   d9 45 2c                fld    DWORD PTR [rbp+0x2c]
  2a:   de c1                   faddp  st(1),st
  2c:   d9 cb                   fxch   st(3)
  2e:   d9 5d d0                fstp   DWORD PTR [rbp-0x30]
  31:   d9 c9                   fxch   st(1)
  33:   d9 5d d4                fstp   DWORD PTR [rbp-0x2c]
  36:   d9 5d d8                fstp   DWORD PTR [rbp-0x28]
  39:   d9 5d dc                fstp   DWORD PTR [rbp-0x24]
  3c:   48 8b 45 d0             mov    rax,QWORD PTR [rbp-0x30]
  40:   48 89 45 f0             mov    QWORD PTR [rbp-0x10],rax
  44:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  48:   48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
  4c:   48 8b 45 e8             mov    rax,QWORD PTR [rbp-0x18]
  50:   48 8b 55 f0             mov    rdx,QWORD PTR [rbp-0x10]
  54:   48 89 10                mov    QWORD PTR [rax],rdx
  57:   48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
  5b:   48 89 50 08             mov    QWORD PTR [rax+0x8],rdx

Po optimalizaci dostaneme tento kód:

   4:   d9 44 24 08             fld    DWORD PTR [rsp+0x8]
   8:   d8 44 24 18             fadd   DWORD PTR [rsp+0x18]
   c:   d9 5c 24 d8             fstp   DWORD PTR [rsp-0x28]
  10:   d9 44 24 0c             fld    DWORD PTR [rsp+0xc]
  14:   d8 44 24 1c             fadd   DWORD PTR [rsp+0x1c]
  18:   d9 5c 24 dc             fstp   DWORD PTR [rsp-0x24]
  1c:   d9 44 24 10             fld    DWORD PTR [rsp+0x10]
  20:   d8 44 24 20             fadd   DWORD PTR [rsp+0x20]
  24:   48 8b 44 24 d8          mov    rax,QWORD PTR [rsp-0x28]
  29:   48 89 07                mov    QWORD PTR [rdi],rax
  2c:   d9 5c 24 e0             fstp   DWORD PTR [rsp-0x20]
  30:   d9 44 24 14             fld    DWORD PTR [rsp+0x14]
  34:   d8 44 24 24             fadd   DWORD PTR [rsp+0x24]
  38:   d9 5c 24 e4             fstp   DWORD PTR [rsp-0x1c]
  3c:   48 8b 44 24 e0          mov    rax,QWORD PTR [rsp-0x20]
  41:   48 89 47 08             mov    QWORD PTR [rdi+0x8],rax

Dělení je realizováno instrukcí FDIV:

  f8:   d8 74 24 18             fdiv   DWORD PTR [rsp+0x18]
  fc:   d9 5c 24 d8             fstp   DWORD PTR [rsp-0x28]
 100:   d9 44 24 0c             fld    DWORD PTR [rsp+0xc]
 104:   d8 74 24 1c             fdiv   DWORD PTR [rsp+0x1c]
 108:   d9 5c 24 dc             fstp   DWORD PTR [rsp-0x24]
 10c:   d9 44 24 10             fld    DWORD PTR [rsp+0x10]
 110:   d8 74 24 20             fdiv   DWORD PTR [rsp+0x20]
 114:   48 8b 44 24 d8          mov    rax,QWORD PTR [rsp-0x28]
 119:   48 89 07                mov    QWORD PTR [rdi],rax
 11c:   d9 5c 24 e0             fstp   DWORD PTR [rsp-0x20]
 120:   d9 44 24 14             fld    DWORD PTR [rsp+0x14]
 124:   d8 74 24 24             fdiv   DWORD PTR [rsp+0x24]
 128:   d9 5c 24 e4             fstp   DWORD PTR [rsp-0x1c]
 12c:   48 8b 44 24 e0          mov    rax,QWORD PTR [rsp-0x20]
 131:   48 89 47 08             mov    QWORD PTR [rdi+0x8],rax

15. Překlad do strojového kódu s použitím instrukcí SIMD

Součet odpovídajících si prvků vektorů je realizován instrukcí ADDPS:

  14:   0f 28 45 f0             movaps xmm0,XMMWORD PTR [rbp-0x10]
  18:   0f 58 45 e0             addps  xmm0,XMMWORD PTR [rbp-0x20]
  1c:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  20:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Rozdíl odpovídajících si prvků vektorů je realizován instrukcí SUBPS:

  3a:   0f 28 45 f0             movaps xmm0,XMMWORD PTR [rbp-0x10]
  3e:   0f 5c 45 e0             subps  xmm0,XMMWORD PTR [rbp-0x20]
  42:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  46:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Součin odpovídajících si prvků vektorů je realizován instrukcí MULPS:

  60:   0f 28 45 f0             movaps xmm0,XMMWORD PTR [rbp-0x10]
  64:   0f 59 45 e0             mulps  xmm0,XMMWORD PTR [rbp-0x20]
  68:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  6c:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Podíl odpovídajících si prvků vektorů je realizován instrukcí DIVPS:

  86:   0f 28 45 f0             movaps xmm0,XMMWORD PTR [rbp-0x10]
  8a:   0f 5e 45 e0             divps  xmm0,XMMWORD PTR [rbp-0x20]
  8e:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
  92:   0f 29 00                movaps XMMWORD PTR [rax],xmm0

Po optimalizaci (-O3) se samotné výpočty zkrátí – nemusí se provádět zbytečné přesuny dat:

Součet odpovídajících si prvků vektorů:

   4:   0f 58 c1                addps  xmm0,xmm1
   7:   0f 29 07                movaps XMMWORD PTR [rdi],xmm0

Rozdíl odpovídajících si prvků vektorů:

  14:   0f 5c c1                subps  xmm0,xmm1
  17:   0f 29 07                movaps XMMWORD PTR [rdi],xmm0

Součin odpovídajících si prvků vektorů:

  24:   0f 59 c1                mulps  xmm0,xmm1
  27:   0f 29 07                movaps XMMWORD PTR [rdi],xmm0

Podíl odpovídajících si prvků vektorů:

  34:   0f 5e c1                divps  xmm0,xmm1
  37:   0f 29 07                movaps XMMWORD PTR [rdi],xmm0

16. Zásadní omezení rozšíření (sic) GCC

V instrukčním souboru všech moderních mikroprocesorů x86–64 nalezneme celou řadu potenciálně velmi užitečných instrukcí, například instrukci PMADDWS, která provádí paralelní součin čtveřice šestnáctibitových hodnot s 32 bitovým mezivýsledkem, s následným součtem prvního + druhého a třetího + čtvrtého mezivýsledku. Užitečné jsou i původní MMX instrukce využitelné při práci s rastrovými obrázky nebo zvukovými vzorky (PACKUSWB, PACKSS, PUNPCKH, PUNPCKL). Podobně nalezneme v SSE instrukce SHUFPS, UNPCKHPS, UNPCKLPS, popř. různé výpočty:

  • SQRTSS, SQRTPS: druhá odmocnina
  • RSQRTSS, RSQRTPS: převrácená hodnota z druhé odmocniny
  • MAXSS, MAXPS: výpočet maxima
  • MINSS, MINPS: výpočet minima

Pro přímé využití těchto (a mnoha dalších podobných) instrukcí se musíme uchýlit k použití intrinsic, což je téma na samostatný článek.

17. SIMD a vektorové operace na architekturách AArch64 a RISC-V

Prakticky každá významnější společnost (v případě mikroprocesorů řady PowerPC pak dokonce aliance několika společností) navrhující mikroprocesory s architekturou RISC přišla dříve či později na trh s instrukční sadou obsahující „vektorové“ instrukce, které jsou dnes souhrnně označovány zkratkou SIMD (původní vektorové instrukce používané na superpočítačích jsou v některých ohledech flexibilnější, proto budeme používat spíše poněkud přesnější zkratku SIMD znamenající „single instruction – multiple data“, viz též předchozí článek). Rozšiřující instrukční sady byly pojmenovávány nejrůznějšími názvy a zkratkami a nikdy vlastně nedošlo – částečně na rozdíl od platformy x86 – ke sjednocení těchto instrukcí do jediné skupiny „SIMD pro RISC“, což je vlastně logické, protože procesory RISC jsou mnohdy určeny pro specializované oblasti použití, od vestavných (embedded) systémů přes smartphony a tablety až po superpočítače.

Nejvýznamnější implementace instrukcí typu SIMD na mikroprocesorech s architekturou RISC, ať již se jedná o instrukce určené pro operace s celými čísly či s čísly reálnými (přesněji řečeno s plovoucí řádovou čárkou), jsou vypsány v následující tabulce:

# Zkratka/název Plný název Rodina procesorů
1 MAX-1 Multimedia Acceleration eXtensions v1 HP-PA RISC
2 MAX-2 Multimedia Acceleration eXtensions v2 HP-PA RISC
3 VIS 1 Visual Instruction v1 Set SPARC V9
4 VIS 2 Visual Instruction v2 Set SPARC V9
5 AltiVec (obchodní názvy Velocity Engine, VMX) PowerPC
6 MDMX MIPS Digital Media eXtension (MaDMaX) MIPS
7 MIPS-3D MIPS-3D MIPS
8 MVI Motion Video Instructions DEC Alpha
9 NEON Advanced SIMD Cortex (ARMv7, ARMv8)
10 Packed SIMD Packed SIMD RISC-V
11 Vector Set Vector Set RISC-V
12 Scalable Vector Extension (SVE) ARMv8.2-A a novější

V navazujícím článku se budeme zabývat těmi SIMD/vektorovými instrukcemi, které jsou implementovány na architekturách AArch64 (NEON) a RISC-V.

18. Příloha – soubor Makefile použitý v dnešním článku

Následující soubor Makefile byl použit pro překlad zdrojových kódů všech výše uvedených demonstračních příkladů do objektového kódu jeho s následným disassemblingem do assembleru (resp. přesněji řečeno do assembleru zkombinovaného s hexadecimálním výpisem obsahu souboru s objektovým kódem):

CC=gcc
OBJDUMP=objdump
 
all:    simd04_1.lst simd04_2.lst \
        simd04B_1.lst simd04B_2.lst \
        simd07_1.lst simd07_2.lst \
        simd08_1.lst simd08_2.lst \
        simd12_1.lst simd12_2.lst \
        simd13_1.lst simd13_2.lst simd13_3.lst simd13_4.lst \
        simd14_1.lst simd14_2.lst simd14_3.lst simd14_4.lst \
        simd15_1.lst simd15_2.lst simd15_3.lst simd15_4.lst \
        simd16_1.lst simd16_2.lst simd16_3.lst simd16_4.lst \
        simd17_1.lst simd17_2.lst simd17_3.lst simd17_4.lst \
        simd18_1.lst simd18_2.lst simd18_3.lst simd18_4.lst
 
clean:
        rm *.lst
        rm *.o

%.lst:  %.o
        objdump -d -M intel -S $< > $@
 
simd04_1.o:     simd04.c
        gcc -c -O0 -mno-sse -g -o $@ $<
 
simd04_2.o:     simd04.c
        gcc -c -O0 -g -o $@ $<
 
simd04B_1.o:    simd04B.c
        gcc -c -O0 -mno-sse -g -o $@ $<
 
simd04B_2.o:    simd04B.c
        gcc -c -O0 -g -o $@ $<
 
simd07_1.o:     simd07.c
        gcc -c -mno-sse -g -o $@ $<
 
simd07_2.o:     simd07.c
        gcc -c -g -o $@ $<
 
simd08_1.o:     simd08.c
        gcc -c -mno-sse -g -o $@ $<
 
simd08_2.o:     simd08.c
        gcc -c -g -o $@ $<
 
simd12_1.o:     simd12.c
        gcc -c -O0 -mno-sse -g -o $@ $<
 
simd12_2.o:     simd12.c
        gcc -c -O0 -g -o $@ $<
 
simd13_1.o:     simd13.c
        gcc -c -O0 -mno-sse -g -o $@ $<
 
simd13_2.o:     simd13.c
        gcc -c -O0 -g -o $@ $<
 
simd13_3.o:     simd13.c
        gcc -c -O3 -mno-sse -g -o $@ $<
 
simd13_4.o:     simd13.c
        gcc -c -O3 -g -o $@ $<
 
simd14_1.o:     simd14.c
        gcc -c -O0 -mno-sse -g -o $@ $<
 
simd14_2.o:     simd14.c
        gcc -c -O0 -g -o $@ $<
 
simd14_3.o:     simd14.c
        gcc -c -O3 -mno-sse -g -o $@ $<
 
simd14_4.o:     simd14.c
        gcc -c -O3 -g -o $@ $<
 
simd15_1.o:     simd15.c
        gcc -c -O0 -mno-sse -g -o $@ $<
 
simd15_2.o:     simd15.c
        gcc -c -O0 -g -o $@ $<
 
simd15_3.o:     simd15.c
        gcc -c -O3 -mno-sse -g -o $@ $<
 
simd15_4.o:     simd15.c
        gcc -c -O3 -g -o $@ $<
 
simd16_1.o:     simd16.c
        gcc -c -O0 -mno-sse -g -o $@ $<
 
simd16_2.o:     simd16.c
        gcc -c -O0 -g -o $@ $<
 
simd16_3.o:     simd16.c
        gcc -c -O3 -mno-sse -g -o $@ $<
 
simd16_4.o:     simd16.c
        gcc -c -O3 -g -o $@ $<
 
simd17_1.o:     simd17.c
        gcc -c -O0 -mno-sse -g -o $@ $<
 
simd17_2.o:     simd17.c
        gcc -c -O0 -g -o $@ $<
 
simd17_3.o:     simd17.c
        gcc -c -O3 -mno-sse -g -o $@ $<
 
simd17_4.o:     simd17.c
        gcc -c -O3 -g -o $@ $<
 
simd18_1.o:     simd18.c
        gcc -c -O0 -mno-sse -g -o $@ $<
 
simd18_2.o:     simd18.c
        gcc -c -O0 -g -o $@ $<
 
simd18_3.o:     simd18.c
        gcc -c -O3 -mno-sse -g -o $@ $<
 
simd18_4.o:     simd18.c
        gcc -c -O3 -g -o $@ $<

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

Demonstrační příklady napsané v jazyku C, které jsou určené pro překlad pomocí překladače GCC C, byly uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/pre­sentations. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již velmi rozsáhlý) repositář:

# Příklad Stručný popis Adresa
1 simd01.c vektor celých čísel typu short int https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd01.c
2 simd02.c ukázka použití vektorů s celočíselnými typy bez znaménka https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd02.c
3 simd03.c ukázka použití vektorů s celočíselnými typy se znaménkem https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd03.c
4 simd04.c paralelní součet celočíselných prvků vektorů https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd04.c
5 simd04B.c úprava pro další datové typy https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd04B.c
6 simd05.c přístup k jednotlivým prvkům vektorů https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd05.c
7 simd05B.c korektnější výpočet počtu prvků vektoru https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd05B.c
8 simd05C.c definice typu vektoru https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd05C.c
9 simd06.c vektor čísel s plovoucí řádovou čárkou https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd06.c
10 simd07.c paralelní součet prvků vektorů (typ float) https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd07.c
11 simd08.c paralelní součet prvků vektorů (typ double) https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd08.c
12 simd09.c překročení délky vektoru https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd09.c
13 simd10.c přístup k jednotlivým prvkům vektorů https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd10.c
14 simd11.c překročení délky vektoru https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd11.c
15 simd12.c dlouhý vektor s 256 bajty https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd12.c
       
16 simd13.c operace součtu pro vektory s celočíselnými prvky rozličné bitové šířky bez znaménka https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd13.c
17 simd14.c operace součtu pro vektory s celočíselnými prvky rozličné bitové šířky se znaménkem https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd14.c
18 simd15.c operace součtu pro vektory s prvky rozličné bitové šířky s plovoucí řádovou čárkou https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd15.c
19 simd16.c operace součtu pro dlouhé vektory s prvky rozličné bitové šířky s plovoucí řádovou čárkou https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd16.c
20 simd17.c všechny podporované binární operace nad vektory s celočíselnými prvky se znaménkem https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd17.c
21 simd18.c všechny podporované binární operace nad vektory s prvky typu float https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd18.c
       
22 Makefile Makefile pro překlad demonstračních příkladů https://github.com/tisnik/pre­sentations/blob/master/SIM­D/Makefile

Soubory vzniklé překladem z jazyka C do assembleru:

# Příklad Stručný popis Adresa
1 simd04_1.lst překlad zdrojového kódu simd04_1.c s přepínači -O0 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd04_1.lst
2 simd04_2.lst překlad zdrojového kódu simd04_2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd04_2.lst
3 simd04B1.lst překlad zdrojového kódu simd04B1.c s přepínači -O0 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd04B1.lst
4 simd04B2.lst překlad zdrojového kódu simd04B2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd04B2.lst
5 simd07_1.lst překlad zdrojového kódu simd07_1.c s přepínači -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd07_1.lst
6 simd07_2.lst překlad zdrojového kódu simd07_2.c s přepínači -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd07_2.lst
7 simd08_1.lst překlad zdrojového kódu simd08_1.c s přepínači -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd08_1.lst
8 simd08_2.lst překlad zdrojového kódu simd08_2.c s přepínači -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd08_2.lst
9 simd12_1.lst překlad zdrojového kódu simd12_1.c s přepínači -O0 -mno-sse -g  https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd12_1.lst
10 simd12_2.lst překlad zdrojového kódu simd12_2.c s přepínači -O0 -g  https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd12_2.lst
11 simd13_1.lst překlad zdrojového kódu simd13_1.c s přepínači -O0 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd13_1.lst
12 simd13_2.lst překlad zdrojového kódu simd13_2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd13_2.lst
13 simd13_3.lst překlad zdrojového kódu simd13_3.c s přepínači -O3 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd13_3.lst
14 simd13_4.lst překlad zdrojového kódu simd13_4.c s přepínači -O3 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd13_4.lst
15 simd14_1.lst překlad zdrojového kódu simd14_1.c s přepínači -O0 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd14_1.lst
16 simd14_2.lst překlad zdrojového kódu simd14_2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd14_2.lst
17 simd14_3.lst překlad zdrojového kódu simd14_3.c s přepínači -O3 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd14_3.lst
18 simd14_4.lst překlad zdrojového kódu simd14_4.c s přepínači -O3 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd14_4.lst
19 simd15_1.lst překlad zdrojového kódu simd15_1.c s přepínači -O0 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd15_1.lst
20 simd15_2.lst překlad zdrojového kódu simd15_2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd15_2.lst
21 simd15_3.lst překlad zdrojového kódu simd15_3.c s přepínači -O3 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd15_3.lst
22 simd15_4.lst překlad zdrojového kódu simd15_4.c s přepínači -O3 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd15_4.lst
23 simd16_1.lst překlad zdrojového kódu simd16_1.c s přepínači -O0 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd16_1.lst
24 simd16_2.lst překlad zdrojového kódu simd16_2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd16_2.lst
25 simd16_3.lst překlad zdrojového kódu simd16_3.c s přepínači -O3 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd16_3.lst
26 simd16_4.lst překlad zdrojového kódu simd16_4.c s přepínači -O3 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd16_4.lst
27 simd17_1.lst překlad zdrojového kódu simd17_1.c s přepínači -O0 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd17_1.lst
28 simd17_2.lst překlad zdrojového kódu simd17_2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd17_2.lst
29 simd17_3.lst překlad zdrojového kódu simd17_3.c s přepínači -O3 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd17_3.lst
30 simd17_4.lst překlad zdrojového kódu simd17_4.c s přepínači -O3 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd17_4.lst
31 simd18_1.lst překlad zdrojového kódu simd18_1.c s přepínači -O0 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd18_1.lst
32 simd18_2.lst překlad zdrojového kódu simd18_2.c s přepínači -O0 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd18_2.lst
33 simd18_3.lst překlad zdrojového kódu simd18_3.c s přepínači -O3 -mno-sse -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd18_3.lst
34 simd18_4.lst překlad zdrojového kódu simd18_4.c s přepínači -O3 -g https://github.com/tisnik/pre­sentations/blob/master/SIM­D/simd18_4.lst

20. Odkazy na Internetu

  1. GCC documentation: Extensions to the C Language Family
    https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions
  2. GCC documentation: Using Vector Instructions through Built-in Functions
    https://gcc.gnu.org/online­docs/gcc/Vector-Extensions.html
  3. SSE (Streaming SIMD Extentions)
    http://www.songho.ca/misc/sse/sse­.html
  4. Timothy A. Chagnon: SSE and SSE2
    http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf
  5. Intel corporation: Extending the Worldr's Most Popular Processor Architecture
    http://download.intel.com/techno­logy/architecture/new-instructions-paper.pdf
  6. SIMD architectures:
    http://arstechnica.com/ol­d/content/2000/03/simd.ar­s/
  7. Tour of the Black Holes of Computing!: Floating Point
    http://www.cs.hmc.edu/~ge­off/classes/hmc.cs105…/sli­des/class02_floats.ppt
  8. 3Dnow! Technology Manual
    AMD Inc., 2000
  9. Intel MMXTM Technology Overview
    Intel corporation, 1996
  10. MultiMedia eXtensions
    http://softpixel.com/~cwrig­ht/programming/simd/mmx.phpi
  11. AMD K5 („K5“ / „5k86“)
    http://www.pcguide.com/ref/cpu/fam/g5K5-c.html
  12. Sixth Generation Processors
    http://www.pcguide.com/ref/cpu/fam/g6­.htm
  13. Great Microprocessors of the Past and Present
    http://www.cpushack.com/CPU/cpu1.html
  14. Very long instruction word (Wikipedia)
    http://en.wikipedia.org/wi­ki/Very_long_instruction_word
  15. CPU design (Wikipedia)
    http://en.wikipedia.org/wi­ki/CPU_design
  16. Bulldozer (microarchitecture)
    https://en.wikipedia.org/wi­ki/Bulldozer_(microarchitec­ture)
  17. SIMD Instructions Considered Harmful
    https://www.sigarch.org/simd-instructions-considered-harmful/
  18. GCC Compiler Intrinsics
    https://iq.opengenus.org/gcc-compiler-intrinsics/
  19. Scalable_Vector_Extension_(SVE)
    https://en.wikipedia.org/wi­ki/AArch64#Scalable_Vector_Ex­tension_(SVE)
  20. FADD/FADDP/FIADD — Add
    https://www.felixcloutier­.com/x86/fadd:faddp:fiadd
  21. ADDPS — Add Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/addps
  22. ADDPD — Add Packed Double-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/addpd
  23. FDIV/FDIVP/FIDIV — Divide
    https://www.felixcloutier­.com/x86/fdiv:fdivp:fidiv
  24. IDIV — Signed Divide
    https://www.felixcloutier­.com/x86/idiv
  25. PADDB/PADDW/PADDD/PADDQ — Add Packed Integers
    https://www.felixcloutier­.com/x86/paddb:paddw:paddd:pad­dq
  26. PSUBB/PSUBW/PSUBD — Subtract Packed Integers
    https://www.felixcloutier­.com/x86/psubb:psubw:psubd
  27. PMULLW — Multiply Packed Signed Integers and Store Low Result
    https://www.felixcloutier­.com/x86/pmullw
  28. PUNPCKLBW/PUNPCKLWD/PUNPCKLDQ/PUN­PCKLQDQ — Unpack Low Data
    https://www.felixcloutier­.com/x86/punpcklbw:punpcklwd:pun­pckldq:punpcklqdq
  29. PUNPCKHBW/PUNPCKHWD/PUNPCKHDQ/PUN­PCKHQDQ — Unpack High Data
    https://www.felixcloutier­.com/x86/punpckhbw:punpckhwd:pun­pckhdq:punpckhqdq
  30. PACKUSWB — Pack with Unsigned Saturation
    https://www.felixcloutier­.com/x86/packuswb
  31. ADDPS — Add Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/addps
  32. SUBPS — Subtract Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/subps
  33. MULPS — Multiply Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/mulps
  34. DIVPS — Divide Packed Single-Precision Floating-Point Values
    https://www.felixcloutier­.com/x86/divps
  35. CBW/CWDE/CDQE — Convert Byte to Word/Convert Word to Doubleword/Convert Doubleword to Quadword
    https://www.felixcloutier­.com/x86/cbw:cwde:cdqe
  36. PAND — Logical AND
    https://www.felixcloutier­.com/x86/pand
  37. POR — Bitwise Logical OR
    https://www.felixcloutier.com/x86/por
  38. PXOR — Logical Exclusive OR
    https://www.felixcloutier­.com/x86/pxor

Byl pro vás článek přínosný?