AVX

Z HPM wiki
Přejít na: navigace, hledání

AVX je nástupce SSE. Zavádí YMM registry délky 256 bitů, se kterými lze pracovat jako s 8 floating point čísly po 32 bitech, nebo jako se 4 floating point čísly po 64 bitech. Operace s celými čísly jsou ale možné jen v dolní polovině YMM registrů, tedy jen se stejným množstvím dat najednou, jako u SSE - využít celou délku YMM registrů pro celočíselné operace lze až s AVX2.

#include <immintrin.h>

Obsah

Odkazy na dokumentaci a software

Příklady instrukcí

Instrukce si můžete vyzkoušet třeba takto (ale jen pokud máte dost nový hardware):

#include <stdio.h>
#include <stdlib.h>
#include <immintrin.h> /* AVX intrinsics */
typedef union {
  float f[ 8 ];
  __m256 v;
} YMM;
main(){
  YMM a = {1,2,3,4,5,6,7,8};
  YMM b = {10,20,30,40,50,60,70,80};
  YMM c;
  int i;
  c.v = _mm256_add_ps( a.v, b.v ); /* instruction to test */
  for( i=0; i<8; i++ ){
    printf("%g ", c.f[i]);
  }
  return 0;
}
gcc -O3 tst.c -mavx -o tst
tst

Čistě vertikální operace s 8 čísly typu float

Tyto operace používají registry délky 256 bitů jako 8 zcela nezávislých čísel délky 32 bitů. Operace se provede se stejnolehlými čísly - provede se 8 operací, které se vzájemně nijak neovlivňují.

_mm256_add_ps VADDPS a + b
_mm256_sub_ps VSUBPS a - b
_mm256_mul_ps VMULPS a * b
_mm256_div_ps VDIVPS a / b

(Dále ještě rcp, sqrt, rsqrt, max, min, andnot, and, or, xor, blendv, roundps.)

Patří sem i některé konverze mezi float a int. "Hardcore" triky založené na detailní znalosti bitové reprezentace čísel umožňují pomocí těchto operací velmi rychle počítat třeba přibližný logaritmus.

Další aritmetické operace s 8 čísly typu float

Operandy i výsledek uvažujeme v pořadí, jako by to byly prvky pole: a0, a1, ..., a7 a b0, b1, ..., b7, i když v registru je to obráceně (a7 je v nejvyšších bitech, kreslených vlevo).


Vertikální, ale nemají všech 8 operací stejných: addsub blend

Horizontální: dp hadd hsub

_mm256_hadd_ps VHADDPS a0+a1, a2+a3, b0+b1, b2+b3, a4+a5, a6+a7, b4+b5, b6+b7
_mm256_hsub_ps VHSUBPS a0-a1, a2-a3, b0-b1, b2-b3, a4-a5, a6-a7, b4-b5, b6-b7

Operace "dot product" je zde naznačena jen přibližně, navíc lze bitovou konstantou zvolit, že se některé součiny nepočítají a že výsledek je umístěn na libovolných místech odpovídající čtveřice:

_mm256_dp_ps VDPPS ... a0*b0+a1*b1+a2*b2+a3*b3 ..., ... a4*b4+a5*b5+a6*b6+a7*b7 ...

(Konstanta ovlivní stejně horní i dolní čtveřici. Nejnižší 4 bity říkají, kam všude dát výsledek. Vyšší 4 bity říkají, které součiny počítat. Například 0 nedělá nic, 0xFF všechno, 0x1F dá a0*b0 čtyřikrát a a4*b4 čtyřikrát, 0x18 dá totéž jen do c3 a c7.)

Přesuny a permutace dat mezi 256-bitovými registry

Instrukce VBLENDPS vybere každý prvek ze stejnolehlé pozice v a či v b, podle bitu v c. Pro c==0 se vše kopíruje z a, pro c==0xFF vše z b a pro c==0x01 je výsledek b0 a1 a2 ... a7:

_mm256_blend_ps VBLENDPS c&1 ? b0 : a0, c&2 ? b1 : a1, c&4 ? b2 : a2, ..., c&128 ? b7 : a7

Instrukce UNPCKLPS, UNPCKHPS a VSHUFPS umožňují jiná promíchání, ale stále jen v rámci jednotlivých čtveřic (s horními i dolními čtveřicemi se provede odpovídající SSE instrukce):

_mm256_unpacklo_ps UNPCKLPS a0, b0, a1, b1, a4, b4, a5, b5
_mm256_unpackhi_ps UNPCKHPS a2, b2, a3, b3, a6, b6, a7, b7
_mm256_shuffle_ps VSHUFPS jako SSE dvakrát, a a b b a a b b

Instrukce VPERMILPS umožňuje v rámci 128-bitových polovin libovolnou permutaci (i vícenásobné zkopírování) 32-bitových hodnot. Permutace se volí konstantou, nebo druhým 256-bitovým operandem (použijí se z něj nejnižší 2 bity v každém 32-bitovém čísle). Pokud je permutace zvolena konstantou, je operace v obou polovinách stejná (konstanta má 8 bitů, vždy 2 určí výběr prvku). Instrukce vlastně dělá totéž co VSHUFPS, jen je vše vybíráno z jednoho registru a ne ze dvou.

Pokud je operace řízena YMM registrem, je z něj použito celkem 16 bitů a v každé 128-bitové polovině lze permutovat jinak.

_mm256_permute_ps VPERMILPS
_mm256_permutevar_ps VPERMILPS

Instrukce VPERM2F128 je vzácný případ cross-lane instrukce, která umožňuje míchat data mezi obvykle oddělenými 128-bitovými polovinami. Do každé ze dvou polovin výsledku lze nezávisle vybrat jednu z pěti možností: libovolné poloviny operandů či nuly. Nejnižší dva bity konstanty (0x00, 0x01, 0x02, 0x03) vybírají postupně a0123, a4567, b0123 a b4567 pro část 0123 výsledku, bit 0x08 nahradí tuto část nulami. Podobně 0x00, 0x10, 0x20, 0x30 vybírají pro část 4567 výsledku a bit 0x80 tuto část nuluje.

_mm256_permute2f128_ps VPERM2F128 a0123/a4567/b0123/b4567/0, a0123/a4567/b0123/b4567/0

Přesuny mezi 256-bitovým registrem a kratšími typy dat

Stejný 32-bitový float z paměti do všech 8 částí registru:

_mm256_broadcast_ss VBROADCASTSS f f f f f f f f

Lze také podobně naplnit registr čtyřmi kopiemi 64-bitů či dvěma kopiemi 128 bitů z paměti.

Instrukce VINSERTF128 umožňuje naplnit spodních či horních 128 bitů 256-bitového registru obsahem z paměti (či z XMM registru) a zbytek kopírovat ze stejných bitů dalšího YMM registru, je tedy také cross-lane.

VEXTRACTF128 umožňuje uložit horních či dolních 128 bitů z 256-bitového registru do paměti či do XMM registru.

Instrukce VMASKMOV umožňuje oběma směry přesun až 256 bitů mezi pamětí a registrem, přičemž je možno například pro jednotlivé 32-bitové části zvolit, zda se přesouvají nebo ne. Je-li cílem registr, budou na místě nepřesunutých částí nuly. Je-li cílem paměť, bude na místě nepřesunutých částí původní obsah. Významnou vlastností instrukce je, že vynechané adresy nezpůsobí případné zbytečné operace cache systému. (Dokonce nezpůsobí ani segfault, pokud se náhodou pohybujeme na okraji segmentu paměti, kam má proces přístup.) V gcc 4.4.5 je v intrinsics chyba v očekávaném typu argumentu, ale cast na mylně požadované __m256 vše spraví.

Testovací program

Řadu zmíněných instrukcí si můžete vyzkoušet, když odkomentujete patřičný řádek:

#include <stdio.h>
#include <stdlib.h>

#include <immintrin.h> /* AVX intrinsics */

typedef union {
  float f[ 8 ];
  __m256 v;
} YMM;
typedef union {
  int i[ 8 ];
  __m256i v;
} YMMi;

main(){
  YMM a = {1,2,3,4,5,6,7,8};
  YMM b = {10,20,30,40,50,60,70,80};
  YMMi bi = {1,0,3,2,3,2,1,0};
  YMMi bi2 = {-1,0,-1,0,0,0,0,0};
  YMM c;
  int i;
  //c.v = _mm256_add_ps( a.v, b.v ); /* instruction to test */
  //c.v = _mm256_mul_ps( a.v, b.v );
  //c.v = _mm256_hadd_ps( a.v, b.v );
  //c.v = _mm256_hsub_ps( a.v, b.v );
  //c.v = _mm256_dp_ps( a.v, b.v, 0xF8 ); /* 0: nothing, 0xFF: everything, 0x1F: a0*b0 four times, a4*b4 four times, 0x18 v c3 a c7 */
  //c.v = _mm256_div_ps( a.v, b.v );
  //c.v = _mm256_blend_ps( a.v, b.v, 0x00 ); /* 0x00: a, 0xFF: b, 0x01: b0 a1 a2 ... a7 */
  //c.v = _mm256_shuffle_ps( a.v, b.v, 0x55 );
  //c.v = _mm256_unpackhi_ps( a.v, b.v );
  //c.v = _mm256_permute2f128_ps( a.v, b.v, 0x12 ); /* any 128-halves or zero */
  //c.v = _mm256_permutevar_ps( a.v, bi.v ); /* any copy/permutation in 128 halves (but the same in both) */
  //c.v = _mm256_broadcast_ss( a.f+3 );

  //c.v = _mm256_maskload_ps( a.f+3, bi.v );  FAILS DUE TO ERROR IN avxintrin.h:
  //   /usr/lib/gcc/x86_64-linux-gnu/4.4.5/include/avxintrin.h:932: note: expected '__m256' but argument is of type '__m256i'
  //c.v = _mm256_maskload_ps( a.f+3, (__m256)bi2.v ); /* cast needed to fix error in avxintrin.h */

  _mm256_maskstore_ps( a.f+3, (__m256)bi2.v, b.v ); c.v = a.v; /* cast needed to fix error in avxintrin.h */

  for( i=0; i<8; i++ ){
    printf("%g ", c.f[i]);
  }
  return 0;
}
Osobní nástroje
Jmenné prostory
Varianty
Akce
Navigace
Nástroje