Instructions SIMD

INF5171: Programmation concurrente et parallèle

Plan de cours

  • Introduction et rappels
  • Parallélisme de tâches
  • Parallélisme de données
  • Hiérarchie de mémoire
  • Instructions SIMD
  • Calcul distribué
  • Mesures de performance
  • Vérification des programmes parallèles
  • Calcul sur processeurs graphiques (GPU)

Version scalaire

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];
}
  • Chaque itération est indépendante
  • On peut faire les additions en parallèle
Parallélisme
de données
  • Déjà vu: découper le tableau en morceaux et les traiter séparément
  • Aujourd’hui: paralléliser en utilisant un seul fil d’exécution

Fausse bonne idée matérielle

  • Ajoutons plus d’unités flottantes scalaires dans le coeur.
flowchart LR L1i["Cache d'instructions"] -->|"32 o/c; 4 i/c"| Decode["Décodage"] Decode -->|"6 µops/c"| Schedule["Ordonnancement"] Schedule --> FPU["Unités flottantes calcul/mémoire"]
  • Il faut décoder/planifier/exécuter plus d’instructions par cycle.
  • Il faut aussi charger a[i], charger b[i], puis écrire c[i].
  • Le débit du flot d’instructions est un goulot d’étranglement.

Un registre, plusieurs valeurs

Registre scalaire 32 bits

[        float        ]
Registre vectoriel 256 bits

[ float ][ float ][ float ][ float ][ float ][ float ][ float ][ float ]

Une instruction, plusieurs opérations

  • Augmenter le travail fait par instruction.
  • Une seule instruction effectue plusieurs opérations indépendantes.
        [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-precision
  • ymm: registre vectoriel 256 bits

Registres x86-64

VueTailleExtensionAnnée
al1 oIntel 80861978
ax2 oIntel 80861978
eax4 oIA-32 (i386)1985
rax8 ox86-642003
xmm016 oSSE1999
ymm032 oAVX2011
zmm064 oAVX-5122016
Scalaires
16 registres généraux 64 bits
SSE/AVX/AVX2
16 registres vectoriels
AVX-512
32 registres vectoriels

Ce que SIMD change

ScalaireSIMD
1 addition par instructionjusqu’à 16 opérations float par instruction
Plusieurs instructions identiquesUne instruction vectorielle
Peu de données par instructionPlus de travail par instruction

En pratique

  • Applications typiques:
    • filtres image/audio, compression, simulation, apprentissage machine.
  • Pour les boucles simples, le compilateur peut générer des instructions SIMD automatiquement.
    • Options utiles: -O3 -march=native.
    • Condition: prouver l’absence de dépendances entre itérations.
  • Sinon: réécriture du code, OpenMP SIMD ou intrinsics .

Version SIMD (vectorielle)

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

À retenir

  • SIMD est du parallélisme de données à l’intérieur d’un coeur.
  • Il exploite des opérations identiques sur des données indépendantes.
  • Il réduit la pression sur le flot d’instructions.
  • Le gain réel dépend aussi des accès mémoire.
  • Il complète les fils d’exécution, il ne les remplace pas.
INF5171: Instructions SIMD
Jaël Champagne Gareau