float
, double
, int
, jne)int a = 1;
int b = 55;
int c = -3;
int r = a * b + c
if(r < 34) {
...
} else {
...
}
bool
-tyyppinen skalaari eli voitaisiin kirjoittaa:bool ret = r < 34; // false, koska c = 1 * 55 + (-3) = 52
if(ret) { ...
float4 va = { 4.6f, 3.4f, -6.9f, 1.0f };
float4 vb = { 5.3f, -7.6f, 5.9f, 0.5f };
float4
-tietotyyppi ei ole millään tavalla standardi vaan eri vektoriohjelmontirajapinnat määrittelevät tyypillisesti omat tietotyyppinsä ja alkeisoperaationsafloat d1 = va.s0; // = vektorimuuttujan va ensimmäinen alkio
float d2 = va.s1; // = vektorimuuttujan va toinen alkio
float d3 = d1 * d2 + vb.s3;
vb.s1 = d3; // vektorimuuttujan vb toinen alkio = d3
void function(float *buff, int n) {
for(int i = 0; i < n; i++) {
float val = buff[i];
float abs_val = fabs(val);
float ret;
if(abs_val < 1.0f)
ret = val*val;
else
ret = abs_val;
buff[i] = ret;
}
}
Time: 0.0583539 s
Flops: 0.666179 GFlops
Max error: 0
__m256
-tyyppiseen vektorimuuttujaan.for(int i = 0; i < n; i += 8) {
...
}
buff
-taulukosta _mm256_load_ps
-aliohjelmakutsulla: __m256 val = _mm256_load_ps(buff+i); // vmovaps ymm, m256
n
on jaollinen 8:lla ja, että osoitinmuuttujan buff
arvo on jaollinen 32:lla sillä _mm256_load_ps
-aliohjelma (vmovaps-käsky) heittää laitteistotason poikkeuksen mikäli sille annettu osoite ei jaollinen 32:lla.val
komponenteille itseisarvot. Tätä varten olemme ennen silmukkaa määritelleet seuraavat muuttujat:const unsigned int abs_coef = 0x7fffffff;
// vbroadcastss ymm, m32
const __m256 abs_mask = _mm256_broadcast_ss((float*) &abs_coef);
abs_coef
pitää sisällään bittimaskin, jonka avulla yksinkertaisen tarkkuuden liukuluvun merkkibitti voidaan nollata eli luvusta otetaan itseisarvo._mm256_broadcast_ss
-aliohjelma palauttaa vektorin (abs_mask
), jonka jokaisen komponentin arvoksi on asetettu argumenttina annetusta muistiosoitteesta löytyvä liukuluku.abs_mask
-vektorin komponentteihin oikeaa liukulukua (0x7fffffff
= NaN
(Not a Number)) vaan käytämme vektorin binääriesitystä bittimaskina.val
ja abs_mask
:__m256 abs_val = _mm256_and_ps(val, abs_mask); // vandps ymm, ymm, ymm
val
komponenteille neliöt:__m256 squ_val = _mm256_mul_ps(val, val); // vmulps ymm, ymm, ymm
_mm256_cmp_ps
-aliohjelmakutsulla.0x00000000
tai 0xffffffff
.const float one_coef = 1.0f;
const __m256 one_mask = _mm256_broadcast_ss(&one_coef);
one_mask
jokaisesta komponentista löytyy liukuluku 1.0f
.Nyt tarvitsemamme "pienempi kuin" -vertailu onnistuu esimerkiksi _CMP_LT_OS
-vertailuoperaattorilla:
// vcmpps ymm, ymm, ymm, imm
__m256 pre = _mm256_cmp_ps(abs_val, one_mask, _CMP_LT_OS);
abs_var
ja squ_val
vektoreista sopivat komponentit._mm256_blendv_ps
-aliohjelmalla, joka ottaa argumentteinaan kaksi vektoria ja ohjausvektorin, joka määrittelee millä tavalla nämä kaksi vektoria tulisi yhdistää uudeksi vektoriksi.pre
-vektoria ohjausvektorina:// vblendvps ymm, ymm, ymm, ymm
_m256 ret = _mm256_blendv_ps(abs_val, squ_val, pre);
ret
-vektorin takaisin buff
taulukkoon:_mm256_store_ps(buff+i, ret); // vmovaps m256, ymm
const unsigned int abs_coef = 0x7fffffff;
const float one_coef = 1.0f;
void function(float *buff, int n) {
const __m256 abs_mask = _mm256_broadcast_ss((float*) &abs_coef);
const __m256 one_mask = _mm256_broadcast_ss(&one_coef);
for(int i = 0; i < n; i += 8) {
__m256 val = _mm256_load_ps(buff+i);
__m256 abs_val = _mm256_and_ps(val, abs_mask);
__m256 squ_val = _mm256_mul_ps(val, val);
__m256 pre = _mm256_cmp_ps(abs_val, one_mask, _CMP_LT_OS);
__m256 ret = _mm256_blendv_ps(abs_val, squ_val, pre);
_mm256_store_ps(buff+i, ret);
}
}
Time: 0.012953 s
Flops: 3.00116 GFlops (estimate)
Max error: 0
pre
-vertailuvektoria._mm256_movemask_ps
-aliohjelmalla, joka palauttaa int
-tyyppisen muuttujan, jonka 8 vähiten merkitsevää bittiä määräytyvät argumenttina annetun vektorin komponenttien eniten merkitsevien bittien mukaan.pre
argumenttina kyseiselle aliohjelmalle ja käyttää paluuarvoa ehtolauseessa:int cond = _mm256_movemask_ps(pre); // vmovmskps r32, ymm
if(cond) {
__m256 squ_val = _mm256_mul_ps(val, val);
__m256 ret = _mm256_blendv_ps(abs_val, squ_val, pre);
_mm256_store_ps(buff+i, ret);
} else {
_mm256_store_ps(buff+i, abs_val);
}
_mm256_movemask_ps
-aliohjelma käytöstä:val
-vektori sisältää komponentteja, joissa \(|x_i| < 1\) eli osa pre
-vertailuvektorin komponenteista on saanut arvon 0xffffffff
. Tällöin näitä elementtejä vastaavat bitit cond
-muuttujassa saavat arvon 1 ja muuttujan cond
totuusarvo vertailulauseessa on true
.val
komponentit toteuttavat ehdon \(1 \leq |x_i|\), joten kaikki pre
-vertailuvektorin komponentit ovat nollia ja siten cond
-muuttuja saa arvon false
.Time: 0.0164099 s
Flops: 2.36895 GFlops (estimate)
Max error: 0
__m256
-tyyppistä vektoria --- a
ja b
--- ja haluaisimme muodostaa uuden vektorin, joka sisältää ne vektoreiden a
ja b
komponentit, joiden indeksinumerot ovat parittomia.union perm_vector {
__m256i m;
__m256 f;
int i[8];
};
const union perm_vector perm1 = { .i = { 1, 3, 5, 7, 0, 2, 4, 6 } };
_mm256_permutevar8x32_ps
-aliohjelmalla (vaatii AVX2-käskykantalaajennuksen), joka ottaa argumentteinaan permutoitavan vektorin ja __m256i
-tyyppisen (8 etumerkillistä 32-bittistä kokonaislukua) permutaatiovektorin. Paluuarvona saadaan permutoitu vektori.Voimme nyt soveltaa perm1
-permutaatiovektoria vektoriin a
:
__m256 aa = _mm256_permutevar8x32_ps(a, perm1.m);
b
:const union perm_vector perm2 = { .i = { 0, 2, 4, 6, 1, 3, 5, 7 } };
__m256 bb = _mm256_permutevar8x32_ps(b, perm2.m);
aa
ja bb
siten että lopullisen vektorin neljä ensimmäistä komponenttia valitaan vektorin aa
alusta ja neljä jälkimmäistä komponenttia vektorin bb
lopusta. Tämä onnistuu tutulla _mm256_blendv_ps
-aliohjelmalla:const union perm_vector blend = { .i = { 0, 0, 0, 0, 1, 1, 1, 1 } };
return _mm256_blendv_ps(aa, bb, blend.f);
#include "immintrin.h"
union perm_vector {
__m256i m;
__m256 f;
int i[8];
};
const union perm_vector perm1 = { .i = { 1, 3, 5, 7, 0, 2, 4, 6 } };
const union perm_vector perm2 = { .i = { 0, 2, 4, 6, 1, 3, 5, 7 } };
const union perm_vector blend = { .i = { 0, 0, 0, 0, 1, 1, 1, 1 } };
__m256 even_elements(__m256 a, __m256 b) {
__m256 aa = _mm256_permutevar8x32_ps(a, perm1.m);
__m256 bb = _mm256_permutevar8x32_ps(b, perm2.m);
return _mm256_blendv_ps(aa, bb, blend.f);
}
__m256d
-tyyppisiä vektorimuuttujia, joihin voidaan tallentaa neljä kaksinkertaisen tarkkuuden liukulukua._mm256_fmadd_pd
-aliohjelmaa, joka ottaa argumenttinaan kolme vektoria ja laskee komponentittaisen fma-operaation (Fused multiply-add) eli ensimmäisen argumentin komponentti kerrotaan toisen argumentin vastaavalla komponentilla ja tulokseen lisätään kolmannen argumentin vastaava komponentti.#include "immintrin.h"
void process(double *a, double *b, int n) {
// Vektoriarvoiset vakiot alkuarvauksen ja Newtonin iteraation laskemiseen
const __m256d coefA = { 48.0/17.0, 48.0/17.0, 48.0/17.0, 48.0/17.0 };
const __m256d coefB = { 32.0/17.0, 32.0/17.0, 32.0/17.0, 32.0/17.0 };
const __m256d coefC = { 1.0, 1.0, 1.0, 1.0 };
// Muokattu silmukka
for(int i = 0; i < n; i += 4) {
__m256d md = - _mm256_load_pd(b+i);
// Alkuarvaus, y_j = 32/17 * (md)_j + 48/17
__m256d y = _mm256_fmadd_pd(coefB, md, coefA);
__m256d t;
// Yksittainen Newtonin iteraatio on jaettu kahteen fma-operaatioon
t = _mm256_fmadd_pd(md, y, coefC); // (t)_j = (md)_j * (y)_j + 1.0
y = _mm256_fmadd_pd(y, t, y); // (y)_j = (y)_j * (t)_j + (y)_j
t = _mm256_fmadd_pd(md, y, coefC);
y = _mm256_fmadd_pd(y, t, y);
t = _mm256_fmadd_pd(md, y, coefC);
y = _mm256_fmadd_pd(y, t, y);
t = _mm256_fmadd_pd(md, y, coefC);
y = _mm256_fmadd_pd(y, t, y);
_mm256_store_pd(b+i, y);
}
}