Hypothèse: a, b et c sont des tableaux de float distincts.
for (uint64_t i = 0; i < n; ++i) {
c[i] = a[i] + b[i];
}
a[i], charger b[i], puis écrire c[i].Registre scalaire 32 bits
[ float ]
Registre vectoriel 256 bits
[ float ][ float ][ float ][ float ][ float ][ float ][ float ][ float ]
[a0 a1 a2 a3 a4 a5 a6 a7] // ymm0
+ [b0 b1 b2 b3 b4 b5 b6 b7] // ymm1
---------------------------
[c0 c1 c2 c3 c4 c5 c6 c7] // ymm2
vaddps ymm2, ymm0, ymm1
ps: packed single-precisionymm: registre vectoriel 256 bits| Vue | Taille | Extension | Année |
|---|---|---|---|
al | 1 o | Intel 8086 | 1978 |
ax | 2 o | Intel 8086 | 1978 |
eax | 4 o | IA-32 (i386) | 1985 |
rax | 8 o | x86-64 | 2003 |
xmm0 | 16 o | SSE | 1999 |
ymm0 | 32 o | AVX | 2011 |
zmm0 | 64 o | AVX-512 | 2016 |
| Scalaire | SIMD |
|---|---|
| 1 addition par instruction | jusqu’à 16 opérations float par instruction |
| Plusieurs instructions identiques | Une instruction vectorielle |
| Peu de données par instruction | Plus de travail par instruction |
-O3 -march=native.C++ avec intrinsics AVX
for (uint64_t i = 0; i < n; i += 8) {
__m256 va = _mm256_loadu_ps(&a[i]);
__m256 vb = _mm256_loadu_ps(&b[i]);
__m256 vc = _mm256_add_ps(va, vb);
_mm256_storeu_ps(&c[i], vc);
}
Assembleur x86-64 représentatif
.Lloop:
vmovups ymm0, [a + 4*i]
vmovups ymm1, [b + 4*i]
vaddps ymm2, ymm0, ymm1
vmovups [c + 4*i], ymm2
add i, 8
cmp i, n
jl .Lloop