Zpracování hodnot typu half float (fp16) na platformě AArch64: operace s vektory

16. 9. 2025
Doba čtení: 42 minut

Sdílet

Procesor ARM Cortex A57 A53
Autor: Open Grid Scheduler / Grid Engine
Navážeme na článek o zpracování skalárů i vektorů s prvky typu half float (FP16). Zabývat se budeme především „vektorovým“ rozšířením překladače GCC i překladem operací s vektory do instrukční sady A64 s rozšířením NEON.

Obsah

1. Zpracování hodnot typu half float (fp16) na platformě AArch64: operace s vektory

2. Dostupné intrinsic funkce pro manipulaci s hodnotami typu half float

3. Podpora pro manipulaci s celými vektory v GCC

4. Vektory obsahující prvky typu half float na platformě AArch64

5. Korektní a nekorektní velikost vektorů

6. Základní aritmetické operace s vektory

7. Způsob překladu aritmetických operací s prvky vektorů do strojového kódu A64

8. Zpracování delších vektorů

9. Základní aritmetické operace s vektory obsahujícími 64 prvků

10. Stejná operace prováděná se všemi prvky vektorů

11. Konverze všech prvků vektoru: z typu half float na typ float

12. Konverze všech prvků vektoru: z typu float na typ half float

13. Operace Multiply–accumulate (MAC) s vektory

14. Součet všech prvků vektoru

15. Alternativní realizace součtu všech prvků vektoru

16. Explicitní výpočet skalárního součinu

17. Alternativní výpočet skalárního součinu

18. Porovnání prvků vektorů s konstantou

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

20. Odkazy na Internetu

1. Zpracování hodnot typu half float (fp16) na platformě AArch64: operace s vektory

Jak již bylo napsáno v perexu článku, dnes navážeme na předchozí článek, ve kterém jsme se věnovali zpracování skalárů i vektorů s prvky typu half float (neboli FP16) na platformě AArch64, tj. na mikroprocesorech s instrukční sadou A64 rozšířenou o technologii NEON. Zatímco minule jsme si ukázali, jak (a zda vůbec) překladač GCC provádí automatickou „vektorizaci“ výpočtů, popř. jakým způsobem se překládají nabízené intrinsic funkce, dnes se zaměříme na sice příbuznou, ale přece jen odlišnou technologii.

Překladač GCC (a nejenom ten – podobnou funkcionalitu totiž nalezneme u prakticky všech moderních překladačů jazyka C) totiž umožňuje definici nových datových typů „vektor“ a nabízí provedení základních aritmetických (i jiných) operací s odpovídajícími si prvky vektorů, aplikaci operace nad prvky vektorů a skalárem atd. Jedná se tedy o technologii, která stojí přibližně na půli cesty mezi nízkoúrovňovými intrinsic na straně jedné a zcela automatickou vektorizací kódu na straně druhé.

2. Dostupné intrinsic funkce pro manipulaci s hodnotami typu half float

V moderních překladačích programovacího jazyka C, přesněji řečeno v těch překladačích, které podporují platformu AArch64, jsou většinou definovány intrinsic funkce (či jen intrinsic), jejichž volání z céčkovského kódu se přímo překládá na instrukce cílové platformy (bez samotného volání funkce, předávání parametrů atd.). Některé příklady jsme si ukázali minule, takže si nyní ve stručnosti připomeneme, jakým způsobem se intrinsic překládají do strojového kódu. Pochopitelně zůstaneme u těch operací, které pracují s hodnotami typu half float

#include <arm_fp16.h>
#include <stdio.h>
 
void fp16_arithm(_Float16 x, _Float16 y) {
    _Float16 a=vaddh_f16(x, y);
    _Float16 b=vsubh_f16(x, y);
    _Float16 c=vmulh_f16(x, y);
    _Float16 d=vdivh_f16(x, y);
 
    // donutíme překladač, aby předchozí intrinsic skutečně vygeneroval do kódu
    printf("%f %f %f %f\n", (double)a, (double)b, (double)c, (double)d);
}

Ve vygenerovaném kódu se na prvních čtyřech řádcích objevuje přímé volání instrukcí, které provádí základní aritmetické operace s hodnotami typu half float; zbytek kódu se týká volání funkce printf (ovšem je zajímavé, jak „levný“ je převod hodnot half float na typ double):

fp16_arithm:
        fdiv    h3, h0, h1
        fmul    h2, h0, h1
        fsub    h31, h0, h1
        fadd    h0, h0, h1
        adrp    x0, .LC0
        add     x0, x0, :lo12:.LC0
        fcvt    d2, h2
        fcvt    d1, h31
        fcvt    d0, h0
        fcvt    d3, h3
        b       printf

Podobných intrinsic existuje celá řada. V následující tabulce jsou vypsány ty intrinsic pro zpracování hodnot typu half float, které lze volat v GCC:

Návratová hodnota Jméno funkce Parametry
float16_t vabsh_f16 float16_t __a
float16_t vaddh_f16 float16_t __a, float16_t __b
int32_t vcvtah_s32_f16 float16_t __a
uint32_t vcvtah_u32_f16 float16_t __a
float16_t vcvth_f16_s32 int32_t __a
float16_t vcvth_f16_u32 uint32_t __a
float16_t vcvthn_f16_s32 int32_t __a, const int __b
float16_t vcvthn_f16_u32 uint32_t __a, const int __b
int32_t vcvthn_s32_f16 float16_t __a, const int __b
uint32_t vcvthn_u32_f16 float16_t __a, const int __b
int32_t vcvth_s32_f16 float16_t __a
uint32_t vcvth_u32_f16 float16_t __a
int32_t vcvtmh_s32_f16 float16_t __a
uint32_t vcvtmh_u32_f16 float16_t __a
int32_t vcvtnh_s32_f16 float16_t __a
uint32_t vcvtnh_u32_f16 float16_t __a
int32_t vcvtph_s32_f16 float16_t __a
uint32_t vcvtph_u32_f16 float16_t __a
float16_t vdivh_f16 float16_t __a, float16_t __b
float16_t vfmah_f16 float16_t __a, float16_t __b, float16_t __c
float16_t vfmsh_f16 float16_t __a, float16_t __b, float16_t __c
float16_t vmaxnmh_f16 float16_t __a, float16_t __b
float16_t vminnmh_f16 float16_t __a, float16_t __b
float16_t vmulh_f16 float16_t __a, float16_t __b
float16_t vnegh_f16 float16_t __a
float16_t vrndah_f16 float16_t __a
float16_t vrndh_f16 float16_t __a
float16_t vrndih_f16 float16_t __a
float16_t vrndmh_f16 float16_t __a
float16_t vrndnh_f16 float16_t __a
float16_t vrndph_f16 float16_t __a
float16_t vrndxh_f16 float16_t __a
float16_t vsqrth_f16 float16_t __a
float16_t vsubh_f16 float16_t __a, float16_t __b
Poznámka: nenechte se zmýlit prefixem „v“
, protože tyto intrinsic nepracují s vektory, ale se skalárními hodnotami.

3. Podpora pro manipulaci s celými vektory v GCC

Intrinsic funkce, které byly zmíněny v předchozí kapitole, mají své specifické využití a vyplatí se alespoň vědět o jejich existenci. Ovšem jak již víme z předchozího článku, některé z nich lze nahradit běžnými operátory jazyka C (nad typy half float). Zajímavější situace ovšem nastane, pokud budeme vyžadovat práci s celými vektory prvků typu half float. I to je možné (do jisté míry) zajistit, protože GCC (ale i některé další překladače) nabízí vývojářům takzvané „vektorové rozšíření“, se kterým jsme se setkali v článcích o SIMD. Vzhledem k tomu, že se jedná o ústřední téma dnešního článku, připomeneme si základní vlastnosti tohoto vektorového rozšíření.

GCC umožňuje definici nových datových struktur typu „vektor prvků X“. Definice takového datového typu obecně vypadá takto:

typedef typ_prvku jméno_typu __attribute__((vector_size(velikost_vektoru_v_bajtech)));
Poznámka: jedná se sice o poměrně kryptický zápis, ovšem ve skutečnosti bude ve zdrojových kódech typicky uveden na jediném místě (v hlavičkovém souboru), kde je taktéž vhodné ho okomentovat.

Podívejme se na následující demonstrační příklad, ve kterém je definován nový datový typ nazvaný v16us (jméno může být pochopitelně jakékoli). Jedná se o vektor o délce šestnácti bajtů, který obsahuje prvky typu short int, což zde konkrétně může znamenat, že se do vektoru vejde celkem osm těchto prvků za předpokladu, že sizeof(unsigned short int)==2:

typedef unsigned short int v16us __attribute__((vector_size(16)));

Velikost jednoho prvku vektoru i velikost celého vektoru získáme operátorem sizeof, což si můžeme snadno ověřit:

#include <stdio.h>
 
typedef unsigned short int v16us __attribute__((vector_size(16)));
 
int main(void)
{
    printf("scalar: %ld bytes\n", sizeof(unsigned short int));
    printf("vector: %ld bytes\n", sizeof(v16us));
 
    return 0;
}

Výsledek by měl vypadat následovně:

scalar: 2 bytes
vector: 16 bytes

Vyzkoušet si můžeme i další vektory o celkové délce 16 bajtů, jejichž prvky budou různých typů a tudíž i délka vektoru měřená v počtu prvků bude odlišná:

#include <stdio.h>
 
typedef unsigned char v16ub __attribute__((vector_size(16)));
typedef unsigned short int v16us __attribute__((vector_size(16)));
typedef unsigned int v16ui __attribute__((vector_size(16)));
typedef unsigned long int v16ul __attribute__((vector_size(16)));
 
int main(void)
{
    printf("unsigned char:  %ld bytes\n", sizeof(unsigned char));
    printf("unsigned short: %ld bytes\n", sizeof(unsigned short int));
    printf("unsigned int:   %ld bytes\n", sizeof(unsigned int));
    printf("unsigned long:  %ld bytes\n", sizeof(unsigned long int));
 
    printf("vector unsigned char:  %ld bytes\n", sizeof(v16ub));
    printf("vector unsigned short: %ld bytes\n", sizeof(v16us));
    printf("vector unsigned int:   %ld bytes\n", sizeof(v16ui));
    printf("vector unsigned long:  %ld bytes\n", sizeof(v16ul));
 
    return 0;
}

Výsledek:

unsigned char:  1 bytes
unsigned short: 2 bytes
unsigned int:   4 bytes
unsigned long:  8 bytes
vector unsigned char:  16 bytes
vector unsigned short: 16 bytes
vector unsigned int:   16 bytes
vector unsigned long:  16 bytes

Totéž platí i pro vektory s prvky se znaménkem:

#include <stdio.h>
 
typedef signed char v16ub __attribute__((vector_size(16)));
typedef signed short int v16us __attribute__((vector_size(16)));
typedef signed int v16ui __attribute__((vector_size(16)));
typedef signed long int v16ul __attribute__((vector_size(16)));
 
int main(void)
{
    printf("signed char:  %ld bytes\n", sizeof(signed char));
    printf("signed short: %ld bytes\n", sizeof(signed short int));
    printf("signed int:   %ld bytes\n", sizeof(signed int));
    printf("signed long:  %ld bytes\n", sizeof(signed long int));
 
    printf("vector signed char:  %ld bytes\n", sizeof(v16ub));
    printf("vector signed short: %ld bytes\n", sizeof(v16us));
    printf("vector signed int:   %ld bytes\n", sizeof(v16ui));
    printf("vector signed long:  %ld bytes\n", sizeof(v16ul));
 
    return 0;
}

Výsledky, které by se měly zobrazit na standardním výstupu po překladu a spuštění tohoto demonstračního příkladu:

signed char:  1 bytes
signed short: 2 bytes
signed int:   4 bytes
signed long:  8 bytes
vector signed char:  16 bytes
vector signed short: 16 bytes
vector signed int:   16 bytes
vector signed long:  16 bytes

4. Vektory obsahující prvky typu half float na platformě AArch64

V dnešním článku nás nejvíce zajímá způsob práce s vektory, jejichž prvky jsou typu half float. Pokusme se tedy nyní definovat typ, který takový vektor popíše. Připomeňme si, že definice typu vektoru vypadá následovně:

typedef typ_prvku jméno_typu __attribute__((vector_size(velikost_vektoru_v_bajtech)));

Konkrétně vektor s osmi prvky typu half float (pro sizeof half_float==2) tedy bude vypadat následovně:

typedef _Float16 float16x8 __attribute__((vector_size(16)));

Opět si pochopitelně můžeme ověřit velikost prvku (musí být dva bajty) i velikost celého vektoru:

#include <stdio.h>
 
typedef _Float16 float16x8 __attribute__((vector_size(16)));
 
int main(void)
{
    printf("scalar: %ld bytes\n", sizeof(_Float16));
    printf("vector: %ld bytes\n", sizeof(float16x8));
 
    return 0;
}

Po překladu a spuštění tohoto příkladu by se mělo vypsat:

scalar: 2 bytes
vector: 16 bytes
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_size.c.

5. Korektní a nekorektní velikost vektorů

V předchozím demonstračním příkladu byla velikost vektoru nastavena na šestnáct bajtů. Jedná se o korektní velikost, a to ze dvou důvodů:

  1. Tato velikost je celočíselným násobkem velikosti jednoho prvku (v bajtech). Konkrétně v tomto případě se jedná o celočíselný násobek dvojky, protože half float v paměti obsadí přesně dva bajty.
  2. Současně je tato velikost celočíselnou mocninou dvojky, což je obecný požadavek GCC, který nezávisí na typu prvků vektoru.

Ostatně můžeme si sami otestovat, jak bude překladač reagovat na pokus o definici vektoru s nekorektní velikostí. Nejprve se pokusíme o definici vektoru, jehož velikost není celočíselným násobkem sizeof(_Float16):

#include <stdio.h>
 
typedef _Float16 float16x8 __attribute__((vector_size(17)));
 
int main(void)
{
    printf("scalar: %ld bytes\n", sizeof(_Float16));
    printf("vector: %ld bytes\n", sizeof(float16x8));
 
    return 0;
}

Překladač tento problém snadno odhalí:

$ gcc -Wall fp16_vector_incorrect_size_1.c
 
fp16_vector_incorrect_size_1.c:3:1: error: vector size not an integral multiple of component size
    3 | typedef _Float16 float16x8 __attribute__((vector_size(17)));
      | ^~~~~~~
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_incorrect_size1.c.

Ve druhém demonstračním příkladu sice bude velikost vektoru dělitelná dvojkou, ale nebude celočíselnou mocninou dvojky:

#include <stdio.h>
 
typedef _Float16 float16x8 __attribute__((vector_size(20)));
 
int main(void)
{
    printf("scalar: %ld bytes\n", sizeof(_Float16));
    printf("vector: %ld bytes\n", sizeof(float16x8));
 
    return 0;
}

I tento problém bude překladačem céčka odhalen:

$ gcc -Wall fp16_vector_incorrect_size_2.c
 
fp16_vector_incorrect_size_2.c:3:1: error: number of vector components 10 not a power of two
    3 | typedef _Float16 float16x8 __attribute__((vector_size(20)));
      | ^~~~~~~
Poznámka: toto druhé omezení je poměrně nepříjemné, protože například neumožňuje snadnou práci s některými embedded modely.
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_incorrect_size2.c.

6. Základní aritmetické operace s vektory

Překladač GCC umožňuje aplikaci základních aritmetických (a popř. i dalších) operátorů nad dvojicí vektorů stejné délky a stejného typu. Tyto operace budou provedeny prvek po prvku, tj. operace se provede pro odpovídající si prvky vektorů s indexem 0, dále s odpovídajícími si prvky vektorů s indexem 1 atd. Důležitý je fakt, že pokud instrukční sada umožňuje provedení těchto operací „paralelně“, tedy s využitím vhodné vektorové instrukce či instrukcí, budou tyto instrukce skutečně použity.

Pro vektory s prvky typu half float jsme omezeni na čtyři základní aritmetické operace a taktéž na porovnání vektorů prvek po prvku. Vyzkoušejme si aritmetickou operaci součtu:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
 
float16x8 add(float16x8 x, float16x8 y)
{
    return x+y;
}

Na platformě AArch64 s instrukcemi pro fp16 bude tato funkce (která interně provádí osm součtů) přeložena do pouhých dvou instrukcí:

add:
        fadd    v0.8h, v0.8h, v1.8h
        ret
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_add1.c. Výsledek překladu do strojového kódu je uložen na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_add1.asm.
Poznámka: překladač provádí kontrolu, zda mají vektory stejnou délku a taktéž stejný typ prvků. To znamená, že další dva příklady se nepřeloží:
typedef _Float16 float16x8 __attribute__((vector_size(16)));
typedef float float32x8 __attribute__((vector_size(32)));
 
float32x8 add(float16x8 x, float32x8 y)
{
    return x+y;
}

Chyba nalezená překladačem:

fp16_vector_add_3.c:6:13: error: invalid operands to binary + (have ‘float16x8’ {aka ‘__vector(8) _Float16’} and ‘float32x8’ {aka ‘__vector(8) float’})
    6 |     return x+y;
      |             ^

Vektory odlišné délky:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
typedef _Float16 float16x16 __attribute__((vector_size(32)));
 
float32x8 add(float16x8 x, float16x16 y)
{
    return x+y;
}

Chyba nalezená překladačem:

fp16_vector_add_4.c:6:13: error: invalid operands to binary + (have ‘float16x8’ {aka ‘__vector(8) _Float16’} and ‘float16x16’ {aka ‘__vector(16) _Float16’})
    6 |     return x+y;
      |             ^

7. Způsob překladu aritmetických operací s prvky vektorů do strojového kódu A64

S celými vektory, které mají stejnou délku a současně i shodný typ prvků half float, je pochopitelně možné provádět i ostatní tři základní aritmetické operace, což je ukázáno na dalším demonstračním příkladu. Připomeňme si, že tyto operace jsou prováděny s odpovídajícími si typy prvků, tj. například operátor * neznačí ani skalární ani vektorový součin, ale skutečně pouze součiny prvků s indexem 0, další součin prvků s indexem 1 atd.:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
 
float16x8 add(float16x8 x, float16x8 y)
{
    return x+y;
}
 
float16x8 sub(float16x8 x, float16x8 y)
{
    return x-y;
}
 
float16x8 mul(float16x8 x, float16x8 y)
{
    return x*y;
}
 
float16x8 div(float16x8 x, float16x8 y)
{
    return x/y;
}

Způsob realizace překladu tohoto příkladu do strojového kódu ukazuje, že se skutečně provádí „vektorizované“ aritmetické operace, tj. vlastně osm aritmetických operací paralelně:

add:
        fadd    v0.8h, v0.8h, v1.8h
        ret
 
sub:
        fsub    v0.8h, v0.8h, v1.8h
        ret
 
mul:
        fmul    v0.8h, v0.8h, v1.8h
        ret
 
div:
        fdiv    v0.8h, v0.8h, v1.8h
        ret
Poznámka: připomeňme si, že existuje větší nabídka operací, které lze provádět s vektory obsahujícími celočíselné prvky. Příkladem mohou být bitové posuny atd.
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_arith1.c. Překlad do strojového kódu viz https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_arith1.asm.

8. Zpracování delších vektorů

Prozatím jsme si otestovali, jakým způsobem se provádí zvolené aritmetické operace s vektory o velikosti 16 bajtů (128 bitů). Vzhledem k tomu, že prvek typu half float obsazuje v paměti 2 bajty (16 bitů), mohou tyto vektory obsahovat osm prvků. Šířka 128 bitů není pochopitelně zvolena náhodně, protože v instrukční sadě NEON na AArch64 mají „vektorové“ registry právě tuto bitovou šířku a tudíž byly (alespoň prozatím) aritmetické operace realizovány jedinou strojovou instrukcí.

Nic nám ovšem nebrání v použití delších vektorů. Musíme pouze dodržet zásadu, že délka (měřená v bajtech) musí být celočíselnou mocninou dvojky. Snadno tedy provedeme součet vektorů s šestnácti prvky typu half float:

typedef _Float16 float16x16 __attribute__((vector_size(32)));
 
float16x16 add(float16x16 x, float16x16 y)
{
    return x+y;
}

Z výsledků překladu tohoto příkladu do strojového kódu je patrné, že se provedl součet pro první polovinu vektorů následovaný součtem pro polovinu druhou. Ovšem současně se změnil i způsob předání parametrů – nyní se parametry předávají referencí a totéž platí pro návratovou hodnotu (to je zajímavé, protože lze teoreticky pro předávání parametrů použít registry v0v7):

add:
        ldp     q29, q31, [x0]
        ldp     q28, q30, [x1]
        fadd    v28.8h, v29.8h, v28.8h
        fadd    v30.8h, v31.8h, v30.8h
        stp     q28, q30, [x8]
        ret
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_add2.c.

9. Základní aritmetické operace s vektory obsahujícími 64 prvků

Pokusme se o další zvětšení vektorů, se kterými se budou provádět aritmetické operace. Konkrétně vektory rozšíříme na 128 bajtů, což pro typy half float znamená, že vektory budou obsahovat 64 prvků. A právě s těmito vektory provedeme všechny čtyři základní aritmetické operace:

typedef _Float16 float16x64 __attribute__((vector_size(128)));
 
float16x64 add(float16x64 x, float16x64 y)
{
    return x+y;
}
 
float16x64 sub(float16x64 x, float16x64 y)
{
    return x-y;
}
 
float16x64 mul(float16x64 x, float16x64 y)
{
    return x*y;
}
 
float16x64 div(float16x64 x, float16x64 y)
{
    return x/y;
}

Z výsledků překladu do strojového kódu je patrné, že se vždy načte dvojice částí registrů (osm prvků) a provede se s nimi požadovaná operace. Následně se výsledek uloží. Tato trojice instrukcí se opakuje celkem 8× a částečně se překrývá s ostatními sedmi opakováními stejné sekvence instrukcí:

add:
        ldp     q17, q19, [x0]
        ldp     q16, q18, [x1]
        ldp     q21, q23, [x0, 32]
        ldp     q20, q22, [x1, 32]
        ldp     q25, q27, [x0, 64]
        ldp     q24, q26, [x1, 64]
        ldp     q29, q31, [x0, 96]
        ldp     q28, q30, [x1, 96]
        fadd    v16.8h, v17.8h, v16.8h
        fadd    v18.8h, v19.8h, v18.8h
        fadd    v20.8h, v21.8h, v20.8h
        fadd    v22.8h, v23.8h, v22.8h
        fadd    v24.8h, v25.8h, v24.8h
        fadd    v26.8h, v27.8h, v26.8h
        stp     q16, q18, [x8]
        fadd    v28.8h, v29.8h, v28.8h
        stp     q20, q22, [x8, 32]
        fadd    v30.8h, v31.8h, v30.8h
        stp     q24, q26, [x8, 64]
        stp     q28, q30, [x8, 96]
        ret
 
sub:
        ldp     q17, q19, [x0]
        ldp     q16, q18, [x1]
        ldp     q21, q23, [x0, 32]
        ldp     q20, q22, [x1, 32]
        ldp     q25, q27, [x0, 64]
        ldp     q24, q26, [x1, 64]
        ldp     q29, q31, [x0, 96]
        ldp     q28, q30, [x1, 96]
        fsub    v16.8h, v17.8h, v16.8h
        fsub    v18.8h, v19.8h, v18.8h
        fsub    v20.8h, v21.8h, v20.8h
        fsub    v22.8h, v23.8h, v22.8h
        fsub    v24.8h, v25.8h, v24.8h
        fsub    v26.8h, v27.8h, v26.8h
        stp     q16, q18, [x8]
        fsub    v28.8h, v29.8h, v28.8h
        stp     q20, q22, [x8, 32]
        fsub    v30.8h, v31.8h, v30.8h
        stp     q24, q26, [x8, 64]
        stp     q28, q30, [x8, 96]
        ret
 
mul:
        ldp     q17, q19, [x0]
        ldp     q16, q18, [x1]
        ldp     q21, q23, [x0, 32]
        ldp     q20, q22, [x1, 32]
        ldp     q25, q27, [x0, 64]
        ldp     q24, q26, [x1, 64]
        ldp     q29, q31, [x0, 96]
        ldp     q28, q30, [x1, 96]
        fmul    v16.8h, v17.8h, v16.8h
        fmul    v18.8h, v19.8h, v18.8h
        fmul    v20.8h, v21.8h, v20.8h
        fmul    v22.8h, v23.8h, v22.8h
        fmul    v24.8h, v25.8h, v24.8h
        fmul    v26.8h, v27.8h, v26.8h
        stp     q16, q18, [x8]
        fmul    v28.8h, v29.8h, v28.8h
        stp     q20, q22, [x8, 32]
        fmul    v30.8h, v31.8h, v30.8h
        stp     q24, q26, [x8, 64]
        stp     q28, q30, [x8, 96]
        ret
 
div:
        ldp     q17, q19, [x0]
        ldp     q16, q18, [x1]
        ldp     q21, q23, [x0, 32]
        ldp     q20, q22, [x1, 32]
        ldp     q25, q27, [x0, 64]
        ldp     q24, q26, [x1, 64]
        ldp     q29, q31, [x0, 96]
        ldp     q28, q30, [x1, 96]
        fdiv    v16.8h, v17.8h, v16.8h
        fdiv    v18.8h, v19.8h, v18.8h
        fdiv    v20.8h, v21.8h, v20.8h
        fdiv    v22.8h, v23.8h, v22.8h
        fdiv    v24.8h, v25.8h, v24.8h
        fdiv    v26.8h, v27.8h, v26.8h
        stp     q16, q18, [x8]
        fdiv    v28.8h, v29.8h, v28.8h
        fdiv    v30.8h, v31.8h, v30.8h
        stp     q20, q22, [x8, 32]
        stp     q24, q26, [x8, 64]
        stp     q28, q30, [x8, 96]
        ret
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_arith2.c.

10. Stejná operace prováděná se všemi prvky vektorů

V další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. Konstanta je přitom stejného datového typu, jaký mají všechny prvky vektoru. 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 – pouze zde důsledně využíváme typ „vektor“:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
 
float16x8 add_delta(float16x8 x, _Float16 delta)
{
    return x+delta;
}

Z výsledku překladu je patrné, že opět došlo k „vektorizaci“ operace součtu. Ovšem nejprve bylo nutné hodnotu parametru delta taktéž převést na vektor instrukcí dup:

add_delta:
        dup     v1.8h, v1.h[0]
        fadd    v0.8h, v1.8h, v0.8h
        ret
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_delta.c. Výsledek překladu do strojového kódu A64 je na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_delta.asm.

11. Konverze všech prvků vektoru: z typu half float na typ float

Některé operace nad celými vektory je nutné provádět s využitím vestavěných funkcí nabízených překladačem GCC. Jedna z těchto funkcí se jmenuje __builtin_convertvector. Jedná se o „generickou“ funkci, která dokáže převést prvky vektoru z původního datového typu na nový datový typ, který je do funkce předán jako druhý parametr (prvním parametrem je převáděný vektor). Už jen z tohoto popisu je zřejmé, že se nemůže jednat o běžnou céčkovskou funkci, ale o specializovaný intrinsic.

Podívejme se nejprve na převod všech prvků vektoru typu half float do nového vektoru se stejným počtem prvků. Nyní ovšem budou mít nové prvky typ float, což mj. znamená, že cílový vektor v operační paměti zabere dvojnásobek místa (8×4 bajty namísto původních 8×2 bajtů):

typedef _Float16 float16x8 __attribute__((vector_size(16)));
typedef float float32x8 __attribute__((vector_size(32)));
 
float32x8 to_float32x8(float16x8 x)
{
    return __builtin_convertvector(x, float32x8);
}

Překlad do strojového kódu je v tomto případě založen na instrukcích fcvtl a fcvtl2. Samotná konverze je tedy rozdělena na dvě instrukce, protože výsledkem musí být vždy maximálně 128bitová hodnota:

to_float32x8:
        fcvtl   v31.4s, v0.4h
        fcvtl2  v0.4s, v0.8h
        stp     q31, q0, [x8]
        ret
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_convert1.c. Výsledek překladu do strojového kódu A64 je na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_convert1.asm.

12. Konverze všech prvků vektoru: z typu float na typ half float

Pochopitelně je podporována i konverze opačným směrem, tj. lze zkonvertovat vektor s prvky typu float na vektor s prvky typu half float. Céčkový kód se až na rozdílné typy parametrů neliší od kódu předchozího:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
typedef float float32x8 __attribute__((vector_size(32)));
 
float16x8 from_float32x8(float32x8 x)
{
    return __builtin_convertvector(x, float16x8);
}

Ovšem ve strojovém kódu je situace v porovnání s předchozím příkladem mnohem složitější. Nyní se prvky konvertují postupně a nikoli celý vektor jako celek. Navíc se výsledný vektor postupně skládá (resp. přesněji řečeno se do něj vkládají prvky) instrukcí INS s následným prokladem prvků instrukcí ZIP. Nejedná se ani o hezký ani o závratně rychlý kód:

from_float32x8:
        ldp     s28, s29, [x0, 8]
        ldp     s31, s30, [x0]
        fcvt    h24, s28
        fcvt    h25, s29
        ldp     s26, s27, [x0, 16]
        fcvt    h31, s31
        fcvt    h30, s30
        ldp     s28, s29, [x0, 24]
        fcvt    h26, s26
        fcvt    h27, s27
        ins     v31.h[1], v24.h[0]
        ins     v30.h[1], v25.h[0]
        fcvt    h28, s28
        fcvt    h29, s29
        ins     v31.h[2], v26.h[0]
        ins     v30.h[2], v27.h[0]
        ins     v31.h[3], v28.h[0]
        ins     v30.h[3], v29.h[0]
        zip1    v0.8h, v31.8h, v30.8h
        ret
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_convert2.c. Výsledek překladu do strojového kódu A64 je na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_convert2.asm.

13. Operace Multiply–accumulate (MAC) s vektory

Při zpracování signálů ale i v dalších oblastech se poměrně často setkáme s operací typu multiply-accumulate, tj. výpočtem a=a+b×c. Tato operace je na platformě AArch64 široce podporována, a to jak pro výpočty se skaláry, tak i pro výpočty s vektory. Ostatně si to můžeme vyzkoušet sami. Pokusíme se o výše uvedený výpočet a=a+b×c, ovšem s tím, že hodnoty a, b a c nejsou skaláry, ale vektory s osmi prvky typu half float:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
 
float16x8 mac(float16x8 a, float16x8 b, float16x8 c)
{
    return a+b*c;
}

Překladač jazyka C (GCC) v tomto případě výpočet rozezná a namísto osmi součinů a osmi součtů zavolá jedinou instrukci FMLA:

mac:
        fmla    v0.8h, v1.8h, v2.8h
        ret
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vector_mac.c. Výsledek překladu do strojového kódu A64 je na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_mac.asm.

14. Součet všech prvků vektoru

Prozatím jsme si vyzkoušeli dva typy operací prováděných s vektory obsahujícími prvky typu half float:

  1. Operace prováděné s odpovídajícími si prvky dvou vektorů (součet, rozdíl, součin, podíl, později i porovnání)
  2. Operace prováděné se všemi prvky a jedinou skalární hodnotou, která je k prvkům přičítána, odečítána atd.

Jednalo se tedy (prozatím) o operace, které by bylo možné naznačit takto:

vektor × vektor → vektor
vektor × skalár → vektor

Nyní k těmto množinám přidáme poslední typ operací:

vektor × vektor → skalár

nebo jen:

vektor → skalár
Poznámka: čtvrtou možnost, tj. běžné skalární operace, jsme si popsali minule.

Mezi nejčastější operaci, která akceptuje na vstupu vektor a výsledkem je skalární hodnota, je součet všech prvků vektoru. Ten můžeme v jazyku C zapsat mnoha různými způsoby. Nejdříve se podívejme na možná poněkud naivní, ale plně funkční způsob – použití počítané programové smyčky:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
 
_Float16 sum1(float16x8 x)
{
    _Float16 sum = 0.0;
    int i;
    for (i=0; i<8; i++) {
        sum += x[i];
    }
    return sum;
}

V tomto případě je překladač GCC relativně úspěšný a zapsanou programovou smyčku rozbalí. Výsledek sice není ideální, ale stále bude rychlejší, než explicitní smyčka, která vždy z vektoru přečte jeden prvek a provede součet:

sum1:
        movi    v31.4s, 0
        ext     v1.16b, v0.16b, v31.16b, #8
        fadd    v0.8h, v1.8h, v0.8h
        ext     v30.16b, v0.16b, v31.16b, #4
        fadd    v0.8h, v30.8h, v0.8h
        ext     v31.16b, v0.16b, v31.16b, #2
        fadd    v0.8h, v31.8h, v0.8h
        ret

15. Alternativní realizace součtu všech prvků vektoru

Zajímavé bude zjistit, co se stane ve chvíli, kdy se budeme snažit překladači „pomoci“ a výpočet sumy rozepíšeme bez použití počítané programové smyčky. Můžeme to realizovat následujícím způsobem:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
 
_Float16 sum2(float16x8 x)
{
    return x[0] + x[1] + x[2] + x[3] + x[4] + x[5] + x[6] + x[7];
}

Výsledkem bude v tomto případě zcela odlišný strojový kód, ve kterém se explicitně provede všech sedm operací součtu a současně se využije celá řada dalších registrů technologie NEON:

sum2:
        dup     h31, v0.h[1]
        dup     h25, v0.h[2]
        dup     h26, v0.h[3]
        dup     h27, v0.h[4]
        fadd    h31, h31, h0
        dup     h28, v0.h[5]
        dup     h29, v0.h[6]
        dup     h30, v0.h[7]
        fadd    h31, h31, h25
        fadd    h31, h31, h26
        fadd    h31, h31, h27
        fadd    h31, h31, h28
        fadd    h31, h31, h29
        fadd    h0, h31, h30
        ret
Poznámka: v tomto případě je pravděpodobně stále nejlepší volat přímo intrinsic nabízené překladačem GCC.

16. Explicitní výpočet skalárního součinu

Dalším velmi často prováděným výpočtem je realizace skalárního součinu (dot product). V dnešním článku si ukážeme nepříliš dobré výsledky překladu realizace tohoto výpočtu do strojového kódu. Nejdříve naprogramujeme výpočet skalárního součinu přesně tak, jak je definován. Vstupem bude dvojice osmiprvkových vektorů s prvky typu half float, výsledkem pak jediná hodnota typu half float:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
 
_Float16 dot_product(float16x8 a, float16x8 b) {
    int i;
    _Float16 result = 0.0;
    for (i=0; i<sizeof(float16x8)/sizeof(_Float16); i++) {
        result += a[i] * b[i];
    }
    return result;
}

Překladač korektně celou smyčku rozbalí a dokonce postupné „skalární“ násobení převede na násobení prvků dvou vektorů. Následně provede postupný součet mezivýsledků:

dot_product:
        movi    v31.4s, 0
        fmul    v0.8h, v0.8h, v1.8h
        ext     v2.16b, v0.16b, v31.16b, #8
        fadd    v0.8h, v2.8h, v0.8h
        ext     v1.16b, v0.16b, v31.16b, #4
        fadd    v0.8h, v1.8h, v0.8h
        ext     v31.16b, v0.16b, v31.16b, #2
        fadd    v0.8h, v31.8h, v0.8h
        ret

Nepatrně lepšího výsledku bude možné dosáhnout použitím intrinsic, což si ukážeme v části věnované optimalizacím.

Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_dot_pro­duct.c. Výsledek překladu do strojového kódu A64 je na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_dot_pro­duct.asm.

Ještě si ověřme, jak se přeloží stejná operace, ovšem s delšími vektory:

typedef _Float16 float16x64 __attribute__((vector_size(128)));
 
_Float16 dot_product(float16x64 a, float16x64 b) {
    int i;
    _Float16 result = 0.0;
    for (i=0; i<sizeof(float16x64)/sizeof(_Float16); i++) {
        result += a[i] * b[i];
    }
    return result;
}

Nyní je způsob překladu odlišný – vidět zde můžeme instrukci fmla pro provedení operace multiply-accumulate, mezivýsledky se nakonec sečtou běžným součtem realizovaným instrukcí fadd:

dot_product:
        ldp     q22, q23, [x0]
        ldp     q21, q30, [x1]
        ldp     q19, q20, [x0, 32]
        ldp     q18, q1, [x1, 32]
        fmul    v30.8h, v23.8h, v30.8h
        ldp     q17, q7, [x0, 64]
        fmul    v1.8h, v20.8h, v1.8h
        ldp     q16, q6, [x1, 64]
        fmla    v30.8h, v22.8h, v21.8h
        fmla    v1.8h, v19.8h, v18.8h
        ldp     q4, q3, [x1, 96]
        ldp     q5, q2, [x0, 96]
        fmla    v30.8h, v17.8h, v16.8h
        fmla    v1.8h, v7.8h, v6.8h
        movi    v0.4s, 0
        fmla    v30.8h, v5.8h, v4.8h
        fmla    v1.8h, v3.8h, v2.8h
        fadd    v1.8h, v1.8h, v30.8h
        ext     v31.16b, v1.16b, v0.16b, #8
        fadd    v31.8h, v31.8h, v1.8h
        ext     v29.16b, v31.16b, v0.16b, #4
        fadd    v29.8h, v29.8h, v31.8h
        ext     v0.16b, v29.16b, v0.16b, #2
        fadd    v0.8h, v0.8h, v29.8h
        ret

17. Alternativní výpočet skalárního součinu

Pro zajímavost se opět překladači pokusíme pomoci rozepsáním počítané smyčky na sumu součinů:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
 
_Float16 dot_product(float16x8 a, float16x8 b) {
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3] + a[4]*b[4] + a[5]*b[5] + a[6]*b[6] + a[7]*b[7];
}

Výsledek bude odlišný, ovšem zajímavé je, že se použije instrukce fmadd, což je varianta operace multiply-accumulate, jejíž vektorovou variantu jsme si již popsali:

dot_product:
        dup     h30, v1.h[1]
        dup     h31, v0.h[1]
        dup     h19, v0.h[2]
        dup     h20, v1.h[2]
        dup     h21, v0.h[3]
        dup     h22, v1.h[3]
        fmul    h31, h31, h30
        dup     h23, v0.h[4]
        fmadd   h31, h1, h0, h31
        dup     h24, v1.h[4]
        dup     h25, v0.h[5]
        dup     h26, v1.h[5]
        fmadd   h31, h19, h20, h31
        dup     h27, v0.h[6]
        dup     h28, v1.h[6]
        dup     h29, v0.h[7]
        fmadd   h31, h21, h22, h31
        dup     h30, v1.h[7]
        fmadd   h31, h23, h24, h31
        fmadd   h31, h25, h26, h31
        fmadd   h31, h27, h28, h31
        fmadd   h0, h29, h30, h31
        ret
Poznámka: zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_dot_pro­duct2.c. Výsledek překladu do strojového kódu A64 je na adrese https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_dot_pro­duct2.asm.

18. Porovnání prvků vektorů s konstantou

Poslední operací s celými vektory, kterou si v dnešním článku ukážeme, je porovnání všech osmi prvků vektoru (s prvky typu half float) s konstantou 0.0. Výsledkem bude taktéž osm prvků, ovšem typu short int (booleovské hodnoty 0 a 1). V jazyku C lze tedy realizovat osm porovnání s nulou a výsledkem bude osm pravdivostních hodnot:

typedef _Float16 float16x8 __attribute__((vector_size(16)));
typedef short int int16x8 __attribute__((vector_size(16)));
 
int16x8 zeros(float16x8 x)
{
    return x==0;
}

Mohlo by se zdát, že se v tomto případě zavolá nějaká instrukce určená pro porovnání vektorů, ovšem ve vygenerovaném strojovém kódu je patrné, že se namísto toho osmkrát volá instrukce fcmeq a navíc byly hodnoty před porovnáním převedeny na typ single osmi instrukcemi fcvt. Výsledek tedy nebude tak rychlý, jak by to mohlo při pohledu na céčkovský zdrojový kód vypadat:

zeros:
        dup     h30, v0.h[1]
        dup     h24, v0.h[3]
        dup     h25, v0.h[2]
        dup     h26, v0.h[5]
        dup     h27, v0.h[4]
        fcvt    s29, h0
        fcvt    s30, h30
        fcvt    s24, h24
        fcvt    s25, h25
        dup     h28, v0.h[7]
        dup     h31, v0.h[6]
        fcvt    s26, h26
        fcmeq   s0, s29, 0
        fcmeq   s24, s24, 0
        fcmeq   s25, s25, 0
        fcvt    s27, h27
        fcmeq   s30, s30, 0
        fcvt    s28, h28
        fcvt    s31, h31
        fcmeq   s26, s26, 0
        ins     v0.h[1], v25.h[0]
        fcmeq   s27, s27, 0
        ins     v30.h[1], v24.h[0]
        fcmeq   s28, s28, 0
        fcmeq   s31, s31, 0
        ins     v30.h[2], v26.h[0]
        ins     v0.h[2], v27.h[0]
        ins     v30.h[3], v28.h[0]
        ins     v0.h[3], v31.h[0]
        zip1    v0.8h, v0.8h, v30.8h
        ret

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

Demonstrační příklady napsané v jazyku C, které jsou určené pro překlad s využitím překladače gcc pro platformu AArch64, byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. 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 fp16_add.c operace součtu dvou hodnot typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add.c
2 fp16_add.asm překlad operace součtu dvou hodnot typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add.asm
3 fp16_add_fp16.asm překlad využívající instrukce pro přímé operace s hodnotami typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add_fp16.asm
       
4 fp16_arith.c všechny čtyři základní aritmetické operace nad typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_arith.c
5 fp16_arith.asm překlad všech čtyř základních aritmetických operací https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_arith.asm
6 fp16_arith_fp16.asm překlad všech čtyř základních aritmetických operací s přímými instrukcemi pro half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_a­rith_fp16.asm
       
7 fp16_comparison.c realizace všech šesti operací pro porovnání dvou hodnot typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_comparison.c
8 fp16_comparison.asm překlad operací pro porovnání dvou hodnot typu half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_com­parison.asm
9 fp16_comparison_fp16.asm překlad operací pro porovnání dvou hodnot typu half float s přímými instrukcemi https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_com­parison_fp16.asm
       
10 fp16_add_delta.c přičtení konstanty ke všem prvkům pole https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add_delta.c
11 fp16_add_delta.asm překlad do strojového kódu bez přímého povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_add_delta.asm
12 fp16_add_delta_fp16.asm překlad do strojového kódu s přímým povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_delta_fp16.asm
       
13 fp16_add_arrays32.c součet odpovídajících si prvků polí typu half float se známou délkou https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32.c
14 fp16_add_arrays32.asm překlad do strojového kódu bez přímého povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32.asm
15 fp16_add_arrays32_fp16.asm překlad do strojového kódu s přímým povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32_fp16.asm
16 fp16_add_arrays32_restrict.c součet odpovídajících si prvků polí typu half float, zajištění, že se pole nepřekrývají https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32_restrict.c
17 fp16_add_arrays32_restrict.asm překlad do strojového kódu s přímým povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays32_restrict.asm
       
18 fp16_add_arrays16.c součet odpovídajících si prvků polí typu half float se známou délkou https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16.c
19 fp16_add_arrays16.asm překlad do strojového kódu bez přímého povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16.asm
20 fp16_add_arrays16_fp16.asm překlad do strojového kódu s přímým povolení manipulace s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16_fp16.asm
21 fp16_add_arrays16_restrict.c součet odpovídajících si prvků polí typu half float, zajištění, že se pole nepřekrývají https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16_restrict.c
22 fp16_add_arrays16_restrict.asm https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_ad­d_arrays16_restrict.asm
       
23 fp16_vector_size.c získání a tisk velikosti prvku typu half float i vektoru s těmito prvky https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vector_size.c
24 fp16_vector_incorrect_size1.c pokus o konstrukci vektoru s neplatnou velikostí https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_incorrect_size1.c
25 fp16_vector_incorrect_size2.c pokus o konstrukci vektoru s neplatnou velikostí https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_incorrect_size2.c
       
26 fp16_vector_add1.c součet odpovídajících si prvků vektorů typu half float, vektory mají délku osmi prvků https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_add1.c
27 fp16_vector_add1.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_add1.asm
28 fp16_vector_add2.c součet odpovídajících si prvků vektorů typu half float, vektory mají délku šestnácti prvků https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_add2.c
29 fp16_vector_add2.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_add2.asm
30 fp16_vector_add3.c pokus o součet vektorů, které mají stejný počet prvků, ovšem odlišného typu https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_add3.c
31 fp16_vector_add4.c pokus o součet vektorů, které mají prvky stejného typu, ovšem odlišnou délku https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_add4.c
       
32 fp16_vector_arith1.c čtyři základní aritmetické operace s vektory typu half float s osmi prvky https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_arith1.c
33 fp16_vector_arith1.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_arith1.asm
34 fp16_vector_arith2.c čtyři základní aritmetické operace s vektory typu half float se šestnácti prvky https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_arith2.c
35 fp16_vector_arith2.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_arith2.asm
       
36 fp16_vector_convert1.c konverze všech prvků vektorů mezi typy float a half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_convert1.c
37 fp16_vector_convert1.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_convert1.asm
38 fp16_vector_convert2.c konverze všech prvků vektorů mezi typy float a half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_convert2.c
39 fp16_vector_convert2.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_convert2.asm
       
39 fp16_vector_delta.c přičtení konstanty ke všem prvkům vektoru https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_delta.c
40 fp16_vector_delta.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_delta.asm
41 fp16_vector_mac.c operace typu Multiply-accumulate s vektory https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vector_mac.c
42 fp16_vector_mac.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_mac.asm
       
43 fp16_vector_sum.c součet všech prvků vektoru https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vector_sum.c
44 fp16_vector_sum.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_sum.asm
45 fp16_dot_product.c skalární součin dvou vektorů https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_dot_product.c
46 fp16_dot_product.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_dot_pro­duct.asm
47 fp16_vector_zeros.c porovnání všech prvků vektoru s nulovou hodnotou https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_zeros.c
48 fp16_vector_zeros.asm překlad do strojového kódu s přímým povolením práce s typy half float https://github.com/tisnik/8bit-fame/blob/master/fp/fp16_vec­tor_zeros.asm

20. Odkazy na Internetu

  1. NEON Technology (stránky ARM)
    https://developer.arm.com/techno­logies/neon
  2. SIMD Assembly Tutorial: ARM NEON – Xiph.org
    https://people.xiph.org/~tte­rribe/daala/neon_tutorial­.pdf
  3. Ne10
    http://projectne10.github.io/Ne10/
  4. NEON and Floating-Point architecture
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/BABIGHEB.html
  5. An Introduction to ARM NEON
    http://peterdn.com/post/an-introduction-to-ARM-NEON.aspx
  6. ARM NEON Intrinsics Reference
    http://infocenter.arm.com/hel­p/topic/com.arm.doc.ihi0073a/I­HI0073A_arm_neon_intrinsic­s_ref.pdf
  7. Arm Neon Intrinsics vs hand assembly
    https://stackoverflow.com/qu­estions/9828567/arm-neon-intrinsics-vs-hand-assembly
  8. ARM NEON Optimization. An Example
    http://hilbert-space.de/?p=22
  9. AArch64 NEON instruction format
    https://developer.arm.com/doc­s/den0024/latest/7-aarch64-floating-point-and-neon/73-aarch64-neon-instruction-format
  10. Vektorové procesory aneb další pokus o zvýšení výpočetního výkonu počítačů
    http://www.root.cz/clanky/vektorove-procesory-aneb-dalsi-pokus-o-zvyseni-vypocetniho-vykonu-pocitacu/
  11. SIMD instrukce využívané v moderních mikroprocesorech řady x86
    http://www.root.cz/clanky/simd-instrukce-vyuzivane-v-modernich-mikroprocesorech-rady-x86/
  12. SIMD instrukce v moderních mikroprocesorech řady x86 (2.část: SSE)
    http://www.root.cz/clanky/simd-instrukce-v-modernich-mikroprocesorech-rady-x86–2-cast-sse/
  13. SIMD instrukce v moderních mikroprocesorech řady x86 (3.část: SSE2)
    http://www.root.cz/clanky/simd-instrukce-v-modernich-mikroprocesorech-rady-x86–3-cast-sse2/
  14. Instrukce typu SIMD na mikroprocesorech RISC
    http://www.root.cz/clanky/instrukce-typu-simd-na-mikroprocesorech-risc/
  15. Instrukce typu SIMD na mikroprocesorech RISC (2. část)
    http://www.root.cz/clanky/instrukce-typu-simd-na-mikroprocesorech-risc-2-cast/
  16. Instrukce typu SIMD na mikroprocesorech RISC (3.část – MIPS-3D a VIS)
    http://www.root.cz/clanky/instrukce-typu-simd-na-mikroprocesorech-risc-3-cast-mips-3d-a-vis/
  17. Trasování a ladění nativních aplikací v Linuxu
    https://www.root.cz/clanky/trasovani-a-ladeni-nativnich-aplikaci-v-linuxu/
  18. Trasování a ladění nativních aplikací v Linuxu: použití GDB a jeho nadstaveb
    https://www.root.cz/clanky/trasovani-a-ladeni-nativnich-aplikaci-v-linuxu-pouziti-gdb-a-jeho-nadstaveb/
  19. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  20. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  21. Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/
  22. Tracing (software)
    https://en.wikipedia.org/wi­ki/Tracing_%28software%29
  23. cgdb: the curses debugger
    https://cgdb.github.io/
  24. cgdb: dokumentace
    https://cgdb.github.io/docs/cgdb-split.html
  25. strace(1) – Linux man page
    http://linux.die.net/man/1/strace
  26. strace (stránka projektu na SourceForge)
    https://sourceforge.net/pro­jects/strace/
  27. strace (Wikipedia)
    https://en.wikipedia.org/wiki/Strace
  28. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  29. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  30. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  31. The LLDB Debugger
    http://lldb.llvm.org/
  32. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  33. Comparison of ARMv8-A cores
    https://en.wikipedia.org/wi­ki/Comparison_of_ARMv8-A_cores
  34. A64 General Instructions
    http://www.keil.com/suppor­t/man/docs/armclang_asm/ar­mclang_asm_pge1427898258836­.htm
  35. ARMv8 (AArch64) Instruction Encoding
    http://kitoslab-eng.blogspot.cz/2012/10/armv8-aarch64-instruction-encoding.html
  36. Cortex-A32 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a32-processor.php
  37. Cortex-A35 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a35-processor.php
  38. Cortex-A53 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a53-processor.php
  39. Cortex-A57 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a57-processor.php
  40. Cortex-A72 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a72-processor.php
  41. Cortex-A73 Processor
    https://www.arm.com/produc­ts/processors/cortex-a/cortex-a73-processor.php
  42. Apple A7 (SoC založen na CPU Cyclone)
    https://en.wikipedia.org/wi­ki/Apple_A7
  43. System cally pro AArch64 na Linuxu
    https://github.com/torval­ds/linux/blob/master/inclu­de/uapi/asm-generic/unistd.h
  44. Architectures/AArch64 (FedoraProject.org)
    https://fedoraproject.org/wi­ki/Architectures/AArch64
  45. SIG pro AArch64 (CentOS)
    https://wiki.centos.org/Spe­cialInterestGroup/AltArch/A­Arch64
  46. The ARMv8 instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  47. A64 Instruction Set
    https://developer.arm.com/pro­ducts/architecture/instruc­tion-sets/a64-instruction-set
  48. Switching between the instruction sets
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  49. The A64 instruction set
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.den0024a/ch05s01.html
  50. Introduction to ARMv8 64-bit Architecture
    https://quequero.org/2014/04/in­troduction-to-arm-architecture/
  51. MCU market turns to 32-bits and ARM
    http://www.eetimes.com/do­cument.asp?doc_id=1280803
  52. Cortex-M0 Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0.php
  53. Cortex-M0+ Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0plus.php
  54. ARM Processors in a Mixed Signal World
    http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world
  55. ARM Architecture (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_architecture
  56. DSP for Cortex-M
    https://developer.arm.com/techno­logies/dsp/dsp-for-cortex-m
  57. Cortex-M processors in DSP applications? Why not?!
    https://community.arm.com/pro­cessors/b/blog/posts/cortex-m-processors-in-dsp-applications-why-not
  58. White Paper – DSP capabilities of Cortex-M4 and Cortex-M7
    https://community.arm.com/pro­cessors/b/blog/posts/white-paper-dsp-capabilities-of-cortex-m4-and-cortex-m7
  59. Q (number format)
    https://en.wikipedia.org/wi­ki/Q_%28number_format%29
  60. TriCore Architecture & Core
    http://www.infineon.com/cms/en/pro­duct/microcontroller/32-bit-tricore-tm-microcontroller/tricore-tm-architecture-and-core/channel.html?channel=ff80808112ab681d0112­ab6b73d40837
  61. TriCoreTM V1.6 Instruction Set: 32-bit Unified Processor Core
    http://www.infineon.com/dgdl/tc_v131_in­structionset_v138.pdf?file­Id=db3a304412b407950112b409b6dd0352
  62. TriCore v2.2 C Compiler, Assembler, Linker Reference Manual
    http://tasking.com/suppor­t/tricore/tc_reference_gu­ide_v2.2.pdf
  63. Infineon TriCore (Wikipedia)
    https://en.wikipedia.org/wi­ki/Infineon_TriCore
  64. C166®S V2 Architecture & Core
    http://www.infineon.com/cms/en/pro­duct/microcontroller/16-bit-c166-microcontroller/c166-s-v2-architecture-and-core/channel.html?channel=db3a304312bef5660112­c3011c7d01ae
  65. Comparing four 32-bit soft processor cores
    http://www.eetimes.com/au­thor.asp?section_id=14&doc_id=1286116
  66. RISC-V Instruction Set
    http://riscv.org/download­.html#spec_compressed_isa
  67. RISC-V Spike (ISA Simulator)
    http://riscv.org/download.html#isa-sim
  68. RISC-V (Wikipedia)
    https://en.wikipedia.org/wiki/RISC-V
  69. David Patterson (Wikipedia)
    https://en.wikipedia.org/wi­ki/David_Patterson_(compu­ter_scientist)
  70. OpenRISC (oficiální stránky projektu)
    http://openrisc.io/
  71. OpenRISC architecture
    http://openrisc.io/architecture.html
  72. Emulátor OpenRISC CPU v JavaScriptu
    http://s-macke.github.io/jor1k/demos/main.html
  73. OpenRISC (Wikipedia)
    https://en.wikipedia.org/wi­ki/OpenRISC
  74. OpenRISC – instrukce
    http://sourceware.org/cgen/gen-doc/openrisc-insn.html
  75. OpenRISC – slajdy z přednášky o projektu
    https://iis.ee.ethz.ch/~gmichi/a­socd/lecturenotes/Lecture6­.pdf
  76. Berkeley RISC
    http://en.wikipedia.org/wi­ki/Berkeley_RISC
  77. Great moments in microprocessor history
    http://www.ibm.com/develo­perworks/library/pa-microhist.html
  78. Microprogram-Based Processors
    http://research.microsoft.com/en-us/um/people/gbell/Computer_Struc­tures_Principles_and_Exam­ples/csp0167.htm
  79. Great Microprocessors of the Past and Present
    http://www.cpushack.com/CPU/cpu1.html
  80. A Brief History of Microprogramming
    http://www.cs.clemson.edu/~mar­k/uprog.html
  81. What is RISC?
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/whatis/
  82. RISC vs. CISC
    http://www-cs-faculty.stanford.edu/~ero­berts/courses/soco/projec­ts/2000–01/risc/risccisc/
  83. RISC and CISC definitions:
    http://www.cpushack.com/CPU/cpu­AppendA.html
  84. FPGA
    https://cs.wikipedia.org/wi­ki/Programovateln%C3%A9_hra­dlov%C3%A9_pole
  85. The Evolution of RISC
    http://www.ibm.com/develo­perworks/library/pa-microhist.html#sidebar1
  86. List of ARM instructions implementing half-precision floating-point arithmetic
    https://stackoverflow.com/qu­estions/76255632/list-of-arm-instructions-implementing-half-precision-floating-point-arithmetic
  87. Half-Precision Floating Point (GCC)
    https://gcc.gnu.org/online­docs/gcc/Half-Precision.html
  88. Additional Floating Types (GCC)
    https://gcc.gnu.org/online­docs/gcc/Floating-Types.html
  89. Advanced SIMD (Neon)
    https://en.wikipedia.org/wi­ki/ARM_architecture_family#Ad­vanced_SIMD_(NEON)
  90. GCC: ARM options
    https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html
  91. Compile ARM Neon intrinsics on macos (M3 chipsets) using clang
    https://stackoverflow.com/qu­estions/79056335/compile-arm-neon-intrinsics-on-macos-m3-chipsets-using-clang
  92. Intrinsics – Arm Developer
    https://developer.arm.com/ar­chitectures/instruction-sets/intrinsics/
  93. FCMEQ (register)
    https://www.scs.stanford.e­du/~zyedidia/arm64/fcmeq_ad­vsimd_reg.html
  94. FCMGE (register)
    https://www.scs.stanford.e­du/~zyedidia/arm64/fcmge_ad­vsimd_reg.html
  95. FCMGT (register)
    https://www.scs.stanford.e­du/~zyedidia/arm64/fcmgt_ad­vsimd_reg.html
  96. A whirlwind tour of AArch64 vector instructions (NEON)
    https://www.corsix.org/con­tent/whirlwind-tour-aarch64-vector-instructions
  97. How is arm_neon.h generated or maintained?
    https://stackoverflow.com/qu­estions/71422209/how-is-arm-neon-h-generated-or-maintained
  98. Arm Neon programming quick reference
    https://community.arm.com/arm-community-blogs/b/operating-systems-blog/posts/arm-neon-programming-quick-reference
  99. SimSIMD
    https://github.com/ashvar­danian/simsimd
  100. SIMD-dot-products-ARM-NEON-RISC-V
    https://github.com/crissmath/SIMD-dot-products-ARM-NEON-RISC-V
  101. Procedure Call Standard for the Arm® 64-bit Architecture (AArch64)
    https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#simd-and-floating-point-registers

Autor článku

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