Upload
phamdan
View
215
Download
0
Embed Size (px)
Citation preview
28 Janvier 2014 Ludovic Saugé, PhD
Optimisations: Impacts des architectures sur les performances des applications
Technical Director of the Center for Excellence in Parallel Programming Applications & Performance Team Bull Extreme Computing BU [email protected]
Séminaire calcul grenoblois
Agenda
• Introduction
• Instruction Level Parallelism – Procésseurs : Généraliés
• Architecture
• Fréquence
• Pipelining
– µarchitectures de l’intel Sandy Bridge • GFLops/sec !
• Roofline model
• Profiling
• Latence & Throughput d’instructions
• Data Level Parallelism: vectorisation – Pourquoi ?
– Comment ?
– Problèmes • Les diagnostiquer
• Les traiter
– Dépendance des données
– Aliasing
– Alignement
– Inlining
– …
• Aspects Mémoires
Introduction
Calculer juste, de manière reproductible et de manière performante.
• Optimiser – « Donner le meilleur rendement possible .»
– Utiliser au mieux les ressources
• Optimisation de code – Loop transformation: unrolling, peeling,
spleeting/peeling, unroll-and-jam, merge …
– Software prefetch
– Cache blocking
Apparemment la même architecture mais pourtant …
❶Intel® Sandy Bridge E5-2670 • Architecture x86_64 • 2.6 GHz
• downclocké à 2.3 GHz • 8 cœurs (2threads/cœurs) • 4 canaux mémoire 1600 MT/s • 2 Liens QPI
❷AMD® Interlagos/Bulldozer 6276 • Architecture x86_64 • 2.3 GHz • 16 cœurs (8 modules FPU) • 4 canaux mémoire 1600 MT/s • 2 Liens HT3
Opteron 6200
DDR3
DDR3
DDR3
DDR3
HT3
HT3 Cores
Xeon® E5-2600
Cores
DDR3
DDR3
DDR3
QPI
QPI
DDR3
Performance pic @ 2.3 GHz d’un double socket: 294,4 Mflops/sec
Apparemment la même architecture mais pourtant …
270
228 241
292
249 279 268
147
0
50
100
150
200
250
300
350
400
32 cores 16 cores
DGEMM HPL CPU
Pe
rfo
rman
ce (
MFl
op
s/se
c)
AMD Opteron 6276 Inel E5-2670 downclocked to 2.3GHz
Higher is Better
❶Intel® Sandy Bridge E5-2670 • Architecture x86_64 • 2.6 GHz
• downclocké à 2.3 GHz • 8 cœurs (2threads/cœurs) • 4 canaux mémoire 1600 MT/s • 2 Liens QPI
❷AMD® Interlagos/Bulldozer 6276 • Architecture x86_64 • 2.3 GHz • 16 cœurs (8 modules FPU) • 4 canaux mémoire 1600 MT/s • 2 Liens HT3
Performance pic @ 2.3 GHz d’un double socket: 294,4 Mflops/sec
54.93 56.62 58.41
115.00
0.00
20.00
40.00
60.00
80.00
100.00
120.00
140.00
icc icc
naive opt intrinsic
Pe
rfo
rman
ce (
GFl
op
s/se
c) AMD Opteron 6276
Inel E5-2670
Higher is Better
Processeurs - caractéristiques
• 32- ou 64-bits
• Fréquence (unité : GHz)
• Cache : niveau, taille, organisation, associativité
• Gestion des accès à la mémoire centrale
– Bus, liens (contrôleur intégrés) → vitesse, débit (GT/sec, GiB/sec)
– Translation d'adresses, TLB ...
• Micro-architecture
– Jeu d’instructions: RISC, CISC, VLIW …
– Caractéristique du FPU
• Largeur
• Nombre d'instruction/cycle
– Nombre et taille des registres
– Les jeux d'instruction supportés (extension SIMD) – parallélisme de données
• MMX, SSE, 3D-Now!, AVX ...
– « Ordering » des instructions :
• IA64, Xeon Phi – In order
• AMD Opteron, Intel Xeon – Out of order
– Pipelinning - parallélisme d'instructions
Hiérarchie mémoire
Registres
Cache
Mémoire locale
Mémoire distante
Disque/Stockage pages
instruction
message
Ligne de cache
Ban
de p
assante
latence
taille
64 bits
32 kB256 kB/2MB
xGB
xGBxPB
1 cycle
4-40 cycles
>100 cycles
Cache
– Localité temporelle ou spatiale des données
– Les caches sont des éléments matériels complexes. • Cette complexité est cachée au programmeur.
– Par contre il existe des règles de bonne usage des caches • Il faut travailler sur la réutilisation des données
• Leur localité
– Le programmeur peut lui même (à avoir) gérer la cohérence de cache • “Explicit cache control”
• Utiliser par les compilateur dans les phases d’optimisation
• Exemple d’instruction ou principe :
– prefetching
– “non temporal stores” ou “streaming stores”
– fence
– flush
Intel IA64 Montecito (1.6 GHz)
Intel Xeon Nehalem (3.2 GHz)
– Design parameters – Capacity (size) – Line size
• Banking
– Coherency • Protocol (Ex. MESI), “snooping”
– Associativity • Direct-mapped • Set-associative • Fully associative
– Block replacement policy • LRU, LFU, FIFO, random
– Write policy • Write-back, write-through (write buffer)
– Allocate-on-write-miss policy
– Victim buffer – Cache unification – Prefetching
Memory
Control Unit
Arithmetic Logic Unit
Accumulator
Input Output
Circuiterie de principe des processeurs (block diagram)
Memory Access
Write Back
Instruction Fetch
Instr. Decode Reg. Fetch
Execute Addr. Calc
L M D
ALU
MU
X
Mem
ory
Reg File
MU
X
MU
X
Data
Mem
ory
MU
X
Sign Extend
4
Ad
de
r Zero?
Next SEQ PC
Ad
dress
Next PC
WB Data
Inst
RD
RS1
RS2
Imm
Pipelining : Exemple, le MIPS (1)
Pipelining : Exemple, le MIPS (2)
Memory Access
Write Back
Instruction Fetch
Instr. Decode Reg. Fetch
Execute Addr. Calc
ALU
Mem
ory
Reg File
MU
X
MU
X
Data
Mem
ory
MU
X
Sign Extend
Zero?
IF/ID
ID/EX
MEM
/WB
EX/M
EM
4
Ad
de
r
Next SEQ PC Next SEQ PC
RD RD RD
WB
Dat
a
Next PC
Ad
dress
RS1
RS2
Imm
MU
X
Pipelining
IF ID EX MEM WB
IF ID EX MEM WB
IF ID EX MEM WB
IF ID EX MEM WB
Inst 1
Inst 2
Inst 3
Inst 4
Cycle 1 Cycle 2 Cycle 3 Cycle 4 Cycle 5 Cycle 6 Cycle 7
s1 s2 e1 e2 m1 m2
1 8 56
Add/Sub
Rounding
Normalize
Add/sub
s3 e3 m3
Fixed-point mantissa mutliplier
330ns
20 ns
50 ns
= 400 ns
s1 s2 e1 e2 m1 m2
1 8 56
Add/Sub
Rounding
Normalize
Add/sub
Fixed-point mantissa mutliplier
s3 e3 m3
Partial Product Generation
Partial Product Reduction
Final Reduction
125 ns
150 ns
55 ns
20 ns
50 ns
= 400 ns
s1 s2 e1 e2 m1 m2
1 8 56
Add/Sub
Rounding
Normalize
Add/sub
s3 e3 m3
Partial Product Generation
Partial Product Reduction
Final Reduction
clock
17
2 n
s 1
72
ns
17
2 n
s
Stage 1
Stage 2
Stage 3
= 172 ns Latence: 516 ns
Exemple de pipelining
3-stage pipeline
– Longest path: product reduction
module, 150ns • +22 ns pour la gestion d’horloge, et
les délais induits par l’ajout des registres
– Augmentation du nombre de transistors (+40%)
– Augmentation du « throughput » • 172 ns vs 400 ns : x2,3
– Augmentation de la latence: 516 ns vs 400 ns
Pipelining : stalls
Le pipeline peut se mettre à “buller”
– Bulles (“bubbles”), “breaks ou pipeline “Stalls” sous certaines conditions (“hazards”): • Structural hazards: conflit de ressources
– Solution: out-of-ordering
• Data hazards: possible quand une instruction depend du résultat de la précédente.
– 3 types » RAW: “Read after Write” ou “Flow dependency”
ADD r0,r1
MUL r2,r0
» WAR: “Write after Read” ou “Anti-dependency”
ADD r4,r1,r3
MUL r1,r2,r3 » WAW: “Write after Write”
ADD r1,r4,r3
MUL r1,r2,r3 – Solutions: Register forwarding, Register renaming, re-ordering, insertion de bulles … – Instruction reordering
• Control hazards: branches, ou autres instructions modifiant la valeur du PC – Solution: prediction de branch
Pipeline : exemple de “péril structurel”
IF ID EX MEM WB
IF ID EX MEM WB
IF ID EX MEM WB
IF ID EX MEM WB
LDR
LDR
ADD
MUL
IF ID EX MEM WB
IF ID EX MEM WB
IF ID EX MEM WB
IF ID EX MEM WB
LDR
LDR
ADD
MUL
(stall)
Prédiction de branches et exécution spéculative
• Eviter la “famine” au niveau du pipeline – Prédire l’avenir avec la connaissance du passé – Spéculer et exécuter ce qu’on pense le plus à même de se passer
• Le coût d’une mauvaise prédiction est important (vidange et remplissage/réamorçage du pipeline)
• Le coût d’une branche est de manière générale important • Attention aux “if”
– Ne pas abuser du “if” • Réduire leur utilisation au strict minimum
– Réserver au débogage – Quand c’est possible, remplacer par des directives de pré-proccesing #if…#else…#endif
• Cf. programmation du kernel: fonctions courtes, “outlinés” • Certaines architectures (ARM) définissent des instructions “conditionnelles”
Need for speed ?
❶ Parallélisme d’instructions (ILP) – Pipelining
• Simple ou super- ou hyper- pipelinning • Instruction : latence (latency) et debit (troughput)
– Superscalaire • Multiplication des unités fonctionnelles (FU ou Port)
– Execution out-of-order (OoO) • “Reordering” et “Register Renaming”
– Prediction de branches et Execution speculative – “Hyperthreading”
❷ Parallèlisme de données (DLP) : SIMD (vectorisation) – NEON, 3DNow!, Altivitec, MMX, SSE(x.y), AVX, AVX-2, AVX-512 …
❸ Parallèlisme sur les tâches (TLP) – Multi- many-cores (intranœud) – Interconnexion de nœuds …
32K Instruction
Cache BPU
MSROM Decoded
ICache
Rename/retirement
Load Store (address)
Integer
MMX/SSE/AVX Low
MMX/SSE/AVX High
Scheduler
Store Data
Legacy Decode Pipeline
256K L2 Cache
32K Data Cache
Micro-op queue
0
1
2
4
5
3
0
1
5
0
1
5
Out-of-order Engine
uncore
Front End
Integer ALU & Shift
Integer ALU & LEA
Load & Store Addr.
Store Data Integer
ALU & LEA
FP Mul
Vector Int Multiply
Vector Logicals
Branch
Divide
Vector Shifts
FP Add
Vector Int ALU
Vector Logicals
Vector Shuffle
Vector Int ALU
Vector Logicals
Po
rt 0
Po
rt 1
Po
rt 4
Po
rt 5
Microarchitecture Sandy Bridge: Unités d’exécution
Po
rt 2
Po
rt 3
- Jusqu’à 6 instructions par cycle: • 3 instructions
mémoires (P2,3,4) • 3 instructions de
calculs (P0,1,5) • En particulier 2
instructions ADD ou MUL en FP
Instruction Set
SP FLOPs per cycle per core
DP FLOPs per cycle per core
L1 Cache Bandwidth (Bytes/cycle)
L2 Cache Bandwidth
(Bytes/cycle)
AVX
(256-bits) 16 8
48 (32B read + 16B write)
32
Sandy Bridge: instruction flottantes et GFLOPS/sec
FP Mul FP Add 2 instructions par cycle Instructions
Opérandes 8 opérations FP32
ou 4 operations par instruction
FP32
YMM: 256 bits
FP32
FP32
FP32
FP32
FP32
FP32
FP32
FP64 FP64 FP64 FP64
16 opérations FP32 ou
8 operations par cycle
Sandy Bridge: instruction flottantes et GFLOPS/sec
FP Mul FP Add 2 instructions par cycle Instructions
Opérandes 8 opérations FP32
ou 4 operations par instruction
FP32
YMM: 256 bits
FP32
FP32
FP32
FP32
FP32
FP32
FP32
FP64 FP64 FP64 FP64
16 opérations FP32 ou
8 operations par cycle
src1 FP64
FP64
FP64
op
FP64
FP64
FP64
op
FP64
FP64
FP64
op
FP64
FP64
FP64
op
src2
dest
Sandy Bridge: instruction flottantes et GFLOPS/sec
Haut degré de parallelisme vectorisation
Intel® Sandy Bridge E5-2670 (Double précision):
micro architecture CPU design
Optimisation
2,6 109 cycles/s/uc × 8 FLOPS/cycle ( 8 cœurs/socket × 2 sockets) ×
Har
dw
are
So
ftw
are
ILP
TLD DLP
Xeon Phi™: instruction flottantes et GFLOPS/sec
Haut degré de parallelisme vectorisation
Intel® Xeon Phi™ 5110P (Double précision):
micro architecture CPU design
Optimisation
1,1 109 cycles/s/uc × 16 FLOPS/cycle (60 cœurs/socket × 1 socket ) ×
Har
dw
are
So
ftw
are
Performance, Puissance, FLOPS/sec …
• Est-ce que seul les GFLOPS/Sec comptent ?
– Top500
• Il faut aussi « bouger les données » et alimenter le processeur.
𝑡 = 𝑡𝑀 + 𝑡𝐶 = 𝑛𝑚𝑡𝑚 + 𝑛𝑐𝑡𝑐 = 𝑛𝑐𝑡𝑐 1 +𝑡𝑚
𝑡𝑐
1
𝑞 avec 𝑞 =
𝑛𝑐
𝑛𝑚 [FLOPS/bytes]
❶ Code Memory bound
❷ Code CPU bound
A
B
𝒒 =𝒏𝒄
𝒏𝒎 [FLOPS/bytes]
Performance, Puissance, FLOPS/sec …
DAXPY DGEMM (naive) DGEMM (mkl)
6.4 GFLops/sec 27.2 GFlops/sec 487.8 GFlops/sec
Ne réinventer pas la roue: utiliser des librairies optimisées! Intel® MKL, AMD® ACML, OpenBLAS …
Roofline Model
• « Roofline model » – La performance est bornée par le « pic
FLOP » théorique de la machine et le produit de la bande passante avec q
1
2
4
8
16
32
64
128
256
512
1024
2048
0.031250.06250.125 0.25 0.5 1 2 4 8 16 32 64 128
Att
ain
able
GFl
op
s/se
c
Compute Intensity (Flops/bytes)
Quel est le « pic » atteignable pour mon application (disons mon kernel) ?
E5-2697v2
CPU bound Domain
Memory bound Domain
Roofline Model
• « Roofline model » – La performance est bornée par le « pic
FLOP » théorique de la machine et le produit de la bande passante avec q
• Il faut estimer le pic réellement atteignable en fonction des caractéristiques de sont code – vectorisation
– Utilisation des opérations flottantes
– …
• Le modèle a ces limites. – Par exemple, Le modèle ne prends pas
en compte les comunications
1
2
4
8
16
32
64
128
256
512
1024
2048
0.031250.06250.125 0.25 0.5 1 2 4 8 16 32 64 128
Att
ain
able
GFl
op
s/se
c
Compute Intensity (Flops/bytes)
Quel est le « pic » atteignable pour mon application (disons mon kernel) ?
Mesure
Pic atteignable
E5-2697v2
Pas de vectorisation
Pas de prefetch
Evaluer les performances d’un code: profiling
• Profiler les codes – gprof
– Utilisations des compteurs hardware • Perf (perf top, record, report …)
• OOProfile
• Instrumenter: PAPI
• Intel® VTUNE (Amplifier XE)
– Instrumenter soi-même son code à la mode «printf » !
Comptage empirique | mesurer le temps
Différentes “sources de temps” sont disponibles
– appel système gettimeofday() – Time Stamp Counter (TSC)
• compteur 64 bits, compte le nombre de cycles depuis le reset • Attention: la façon exacte dont il est incrémenté dépend du processeur • peut être lu avec l'instruction rdtsc
– Disponible pour l’utilisateur
• Problemes : – La fonction n’est pas sérialisante (out-of-ordering)
» Solution : faire précéder la rdtsc d’un appel à l’instruction cpuid – Cache instruction miss … – Overhead rtdsc+cpuid : >200 cycles ! Il faut le prendre en compte …
– Autres compteurs matériels • Précis • Nécessite d’utiliser les instruction privilégiées rdmsr et wrmsr (“ring 0”).
– Module d’interfaçage avec le noyau, au moins pour “enabler” les compteurs de performances …
cpuid
rdtsc ; read time stamp
mov time, eax ; move counter into variable
fdiv ; floating-point divide
cpuid
rdtsc ; read time stamp
sub eax, time ; find the differenc
Comptage empirique | mesurer le temps
#define SERIALIZE() { \
asm volatile ("xorl %%eax,%%eax":::"eax");\
asm volatile ("cpuid":::"eax","ebx","ecx","edx"); \
}
typedef unsigned long long ticks;
ticks getticks(void)
{
unsigned a, d;
SERIALIZE();
asm volatile("rdtsc" : "=a" (a), "=d" (d));
return ((ticks)a) | (((ticks)d) << 32);
}
int main () {
double t0,t1;
ticks T0,T1;
T0=getticks();
t0=dtime();
sleep(1);
t1=dtime();
T1=getticks();
printf("%.1f\n",(double)(T1-T0)/(t1-t0)*1e-6) ;
return 0;
}
Sandy Bridge: instruction flottantes et GFLOPS/sec
Kernel Code Cycles par opération(*)
SAXPY for(inti=1; i<N; i++)
s[i] = a * x[i] + y[i]; 2.32 cycles
SASPY for(inti=1; i<N; i++)
s[i] = a * s[i-1] + y[i]; 14.1 cycles
(*) nombre total de cycle / N Pas de vectorisation, ni unrolling, données résidant dans le cache L1
movsd xmm1, QWORD PTR [array+rdx*8]
mulsd xmm1, xmm0
addsd xmm1, QWORD PTR [r9+rdx*8]
movsd QWORD PTR [r8+rdx*8], xmm1
inc rdx
cmp rdx, 2048
jb ..B1.35
movsd xmm1, QWORD PTR [65528+array+rdx*8]
mulsd xmm1, xmm0
addsd xmm1, QWORD PTR [r13+rdx*8]
movsd QWORD PTR [r8+rdx*8], xmm1
inc rdx
cmp rdx, 2048
jb ..B1.27
for(inti=1; i<N; i++) // SASPY
s[i] = a * S[i-1] + y[i]; :w
for(inti=1; i<N; i++) // SAXPY
s[i] = a * x[i] + y[i];
Inspection du code Assembleur: (-S –masm=intel)
Même séquence d’instructions !
Throughput et Latence d’instructions
Deux notions importantes: – Throughput: fréquence à laquelle une instruction peut-être émise,
dans le meilleur des cas sans dépendance de données;
– Latence: temps nécessaire pour qu’une instruction se termine et fournisse son résultat ;
Timing (cycles) Inst r0,r1
Inst r2,r1
5 cycles de latence 1 instruction par cycle
Timing (cycles)
4 cycles de latence
2 instructions par cycle
Timing (cycles) Inst r0, r1
Inst r2, r0
Inst r3, r1
Inst r4, r3
Throughput et Latence d’instructions
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
movsd xmm1, QWORD PTR [] 1 CP
mulsd xmm1, xmm0 1 CP
addsd xmm1, QWORD PTR [r9+rdx*8] 1 CP
movsd QWORD PTR [r8+rdx*8], xmm1
inc rdx
cmp rdx, 2048
jb ..B1.35
Cycles
iaca -64 -arch SNB -analysis LATENCY saspy.o
Intel(R) Architecture Code Analyzer Version - 2.1
…
Latency Analysis Report
---------------------------
Latency: 15 Cycles
…
| Inst | Resource Delay In Cycles | |
| Num | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | FE | |
---------------------------------------------------------------
| 0 | | | | | | | | CP | movsd xmm1, qword ptr [r8+rdx*8-0x8]
| 1 | | | | | | | | CP | mulsd xmm1, xmm0
| 2 | | | | | | | | CP | addsd xmm1, qword ptr [r13+rdx*8]
| 3 | | | | | | | 1 | CP | movsd qword ptr [r8+rdx*8], xmm1
| 4 | | | | | | | 1 | | inc rdx
| 5 | | | | | | | | | cmp rdx, 0x800
| 6 | | | | | | | | | jb 0xffffffffffffffd6
Latence 15 cycles
Throughput 1-2 cycles
Throughput et Latence d’instructions
Sandy Bridge DIVPS SQRTPS DIVPS SQRTPD
Latence 28 28 42 42
Troughput 28 28 42 42
Ivy Bridge DIVPS SQRTPS DIVPS SQRTPD
Latence 21 21 35 35
Troughput 14 14 28 28
Vectorisation | Exemple de SSE et AVX
x x
XMM0
XMM1
XMM2
x x
YMM0
YMM1
YMM2
x x
SSE AVX
128-bits 256-bits
= 2 opérations simultanées = 4 opérations simultanées
Une Instruction …
YMM0
256-bits
XMM0
128-bits
Quelles méthodes pour vectoriser ?
Compiler: Auto-vectorization (no change of code)
Compiler: Auto-vectorization hints (#pragma vector, …)
SIMD intrinsic class (e.g.: F32vec, F64vec, …)
Vector intrinsic (e.g.: _mm_fmadd_pd(…), _mm_add_ps(…), …)
Assembler code (e.g.: [v]addps, [v]addss, …)
Compiler: Intel® Cilk™ Plus Array Notation Extensions
Co
mp
ilate
ur
Low
-lev
el p
rogr
amm
ing
Facilité d’utilisation
Contrôle
Quelles méthodes pour vectoriser ?
High level programming
Low level programming
Standard Propriétaire
Auto-vectorization
Auto-vectorization hints
SIMD intrinsic class
Vector intrinsic
Assembler code
Intel® Cilk™ Plus Array Notation Extensions
OpenMP 4.0
Ecouter les conseils du compilateur: reporting
n Diagnostic Messages
0 Tells the vectorizer to report no diagnostic information. Useful for turning off reporting in case it was enabled on command line earlier.
1 Tells the vectorizer to report on vectorized loops. [default if n missing]
2 Tells the vectorizer to report on vectorized and non-vectorized loops.
3 Tells the vectorizer to report on vectorized and non-vectorized loops and any proven or assumed data dependences.
4 Tells the vectorizer to report on non-vectorized loops.
5 Tells the vectorizer to report on non-vectorized loops and the reason why they were not vectorized.
6*
Tells the vectorizer to use greater detail when reporting on vectorized and non-vectorized loops and any proven or assumed data dependences.
Compilateur Intel: -vec-report=<n>
Compilateur gcc: -ftree-vectorizer-verbose=<n>
Diagnostiquer …
vmulpd ymm1, ymm0, YMMWORD PTR [rax+r14]
vaddpd ymm2, ymm1, YMMWORD PTR [rdx+r14]
vmovntpd YMMWORD PTR [rdi+r14], ymm2
#pragma omp parallel for
#pragma unroll(4)
for(i=0;i<N;i+=1) {
C[i]=alpha*A[i]+B[i];
}
Sans vectorisation
Avec vectorisation
[s,d]
Single Double
AVX
{v} OP
Scalar Packed
[s,p] movsd xmm1, QWORD PTR [8+r9+rdi]
mulsd xmm0, xmm4
addsd xmm0, QWORD PTR [r9+rcx]
movsd QWORD PTR [r9+r8], xmm0
• Demander le reporting au compilateur
• Editer l’assembleur …
– Générer l’assembleur à la compilation: « -S –masm=intel »
– Désassembler un binaire ou un fichier objet: « objdump -M intel –D mon_binaire.x»
Diagnostiquer …
• Demander le reporting au compilateur
• Editer l’assembleur …
– Générer l’assembleur à la compilation: « -S –masm=intel »
– Désassembler un binaire ou un fichier objet: « objdump -M intel –D mon_binaire.x»
• Par l’expérience:
– Comparer les différents niveaux de vectorisation: • AVX, SSE
• Sans vectorisation: « -no-vec »
• Malgré tout je n’observe pas de bénéfice de la vectorisation, pourquoi ?
Diagnostiquer …
vmulpd ymm1, ymm0, YMMWORD PTR [rax+r14]
vaddpd ymm2, ymm1, YMMWORD PTR [rdx+r14]
vmovntpd YMMWORD PTR [rdi+r14], ymm2
movups xmm1, XMMWORD PTR [r8+r11]
mulpd xmm1, xmm0
addpd xmm1, XMMWORD PTR [r9+r11]
movntpd XMMWORD PTR [rdx+r11], xmm1
export OMP_NUM_THREADS=16
export KMP_AFFINITY=verbose,compact
./triad.x 1.000001
movsd xmm1, QWORD PTR [8+r9+rdi]
mulsd xmm0, xmm4
addsd xmm0, QWORD PTR [r9+rcx]
movsd QWORD PTR [r9+r8], xmm0
Mbytes/sec MFLops/sec
AVX 76,065 6,338
SSE 76,382 6,365 no-vec 60,382 5,031
-no-vec
SSE 4.2
AVX
#pragma omp parallel for
#pragma unroll(4)
for(i=0;i<N;i+=1) {
C[i]=alpha*A[i]+B[i];
}
0
1000
2000
3000
4000
5000
6000
7000
AVX SSE no-vec
MFLops/sec
Bande Passante mémoire est un facteur limitant !
Problèmes à la vectorisation
● Dépendance des données : attention à l’“aliasing”, design,
pragma “hints”
Dépendances des données
for(i=2;i<=N; i++)
a[i]=a[i-1]+b[i];
a[2] = a[1] + b[2];
a[3] = a[2] + b[3];
a[4] = a[3] + b[4];
a[5] = a[4] + b[5];
for(i=1;i<=N-1;i++)
a[i]=a[i+1]+b[i];
a[1] = a[2] + b[1];
a[2] = a[3] + b[2];
a[3] = a[4] + b[3];
a[4] = a[5] + b[4];
WAR : « write after read » RAW : « read after write »
Non vectorisable Vectorisable
Dépendances des données
• Autres dépendances:
– RAR : « read after read »
• Vectorisable Pas réellement un dépendance.
– WAW : « read after write »
• Non vectorisable
for(i=1;i<=N; i++)
a[i]=b[i%M]+c[i];
for(i=1;i<=N; i++)
a[i%M]=b[i]+c[i];
Dépendances des données
void scale(double *A,const int k, const double alpha,const int N)
{
int i;
#pragma ivdep
for(i=0;i<N;i+=1)
A[i] = alpha*A[i+k];
}
icc -c vec.c -vec-report=6
vec.c(6): (col. 2) remark: vectorization support: unroll factor set to 4.
vec.c(6): (col. 2) remark: LOOP WAS VECTORIZED.
vec.c(6): (col. 2) remark: loop was not vectorized: existence of vector dependence.
vec.c(7): (col. 3) remark: vector dependence: assumed ANTI dependence between A line 7 and A line 7.
vec.c(7): (col. 3) remark: vector dependence: assumed FLOW dependence between A line 7 and A line 7.
vec.c(7): (col. 3) remark: vector dependence: assumed FLOW dependence between A line 7 and A line 7.
vec.c(7): (col. 3) remark: vector dependence: assumed ANTI dependence between A line 7 and A line 7.
vec.c(6): (col. 2) remark: loop skipped: multiversioned.
icc -c vec.c -vec-report=6
vec.c(7): (col. 2) remark: vectorization support: unroll factor set to 4.
vec.c(7): (col. 2) remark: LOOP WAS VECTORIZED.
gcc -c vec.c \
-O3 -ftree-vectorize -ftree-vectorizer-verbose=2 \
-march=corei7-avx -mtune=corei7-avx
Vectoriser …
alloc_section
distribute_point
inline, noinline,forceinline
ivdep
loop_count
memref_control
novector
optimize
optimization_level
prefetch/noprefetch
simd
unroll/nounroll
unroll_and_jam/nounroll_and_jam
Vector (always/aligned/assert/unaligned/nontemporal/temp
oral)
C/C++ #pragma <keyword>
Fortran !DEC$ <keyword>
Dépendances des données et aliasing
void my_combine(int * ioff, int nx, double * a, double * b, double * c)
{
int i;
#pragma ivdep
for(i=0; i<nx; i++)
a[i]=b[i]+c[i+*ioff];
}
icc -c combine.c -vec-report=3
combine.c(4): (col. 2) remark: loop was not vectorized: existence of vector dependence.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between c line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and c line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and c line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between c line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between ioff line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and ioff line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and ioff line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between ioff line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between b line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and b line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and b line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between b line 5 and a line 5.
icc -c combine.c -vec-report=3
combine.c(5): (col. 2) remark: loop was not vectorized: vectorization possible but seems inefficient.
Dépendances des données et aliasing
void my_combine(int * ioff, int nx, double * a, double * b, double * c)
{
int i;
#pragma simd
for(i=0; i<nx; i++)
a[i]=b[i]+c[i+*ioff];
}
icc -c combine.c -vec-report=3
combine.c(4): (col. 2) remark: loop was not vectorized: existence of vector dependence.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between c line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and c line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and c line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between c line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between ioff line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and ioff line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and ioff line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between ioff line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between b line 5 and a line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and b line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed FLOW dependence between a line 5 and b line 5.
combine.c(5): (col. 3) remark: vector dependence: assumed ANTI dependence between b line 5 and a line 5.
icc -c combine.c -vec-report=3
combine.c(5): (col. 2) remark: loop was not vectorized: vectorization possible but seems inefficient.
icc -c combine.c -vec-report=6
combine.c(5): (col. 2) remark: vectorization support: unroll factor set to 4.
combine.c(5): (col. 2) remark: SIMD LOOP WAS VECTORIZED.
Dépendances des données et aliasing
void my_combine(
int * ioff, int nx,
double * a, double * b, double * c)
{
int i;
for(i=0; i<nx; i++)
a[i]=b[i]+c[i+*ioff];
}
icc -c combine.c -vec-report=6 -ansi-alias
combine.c(5): (col. 2) remark: vectorization support: unroll factor set to 4.
combine.c(5): (col. 2) remark: LOOP WAS VECTORIZED.
combine.c(5): (col. 2) remark: loop skipped: multiversioned.
Indiquer au compilateur que vous adhérer (!) au ISO C Standard aliasability rules dans votre style de programmation (pas d’aliasing de pointeurs).
Attention: portée globale de l’option …
Dépendances des données et aliasing
void my_combine(
int * ioff, int nx,
double * restrict a, double * restrict b, double * restrict c)
{
int i;
for(i=0; i<nx; i++)
a[i]=b[i]+c[i+*ioff];
}
icc -c combine.c -vec-report=6 -restrict
combine.c(4): (col. 2) remark: vectorization support: unroll factor set to 4.
combine.c(4): (col. 2) remark: LOOP WAS VECTORIZED.
icc -c combine.c -vec-report=6 -ansi-alias
combine.c(5): (col. 2) remark: vectorization support: unroll factor set to 4.
combine.c(5): (col. 2) remark: LOOP WAS VECTORIZED.
combine.c(5): (col. 2) remark: loop skipped: multiversioned.
Indiquer au compilateur que vous adhérer (!) au ISO C Standard aliasability rules dans votre style de programmation (pas d’aliasing de pointeurs).
Attention: portée globale de l’option …
Problèmes à la vectorisation
● Dépendance des données : attention à l’“aliasing” design,
pragma “hints”
● Alignement des données : aligner vos données, pragma “hints”
Alignement des données
64 bytes 64 bytes
16 16 16 16 16 16 16 16
16 16 16 16 16 16 16 16
32 32 32 32
32 32 32 32
Lignes de cache
Non-alignées
Non-alignées
Alignées
Alignées
• Peut être pénalisant. Mieux vaut aligner les données sur la taille des vecteurs.
64 bytes
Alignement des données
• Peut être pénalisant. Mieux vaut aligner les données sur la taille des vecteurs – Différentes méthodes:
• Allocation: posix_memalign(),_mm_malloc(),…
• Statique: __attribute__((aligned(32))),__declspec(align(32))
• Indiquer au compilateur que les données sont alignées – Attention: il ne suffit de le dire, il faut le faire ;-)
#define N 1000000000
double A[N] ;
double B[N] ;
double S[N] ;
#pragma vector aligned(A,B,C)
for(i=0;i<N;i+=1)
{
C[i]=0.0;
A[i]=1.0;
B[i]=0.1;
}
#define N 1000000000
double A[N] __attribute__((aligned(64)));
double B[N] __attribute__((aligned(64)));
double S[N] __attribute__((aligned(64)));
#pragma vector aligned(A,B,C)
for(i=0;i<N;i+=1)
{
C[i]=0.0;
A[i]=1.0;
B[i]=0.1;
}
Alignement des données
#define N 1000000000
double A[N] __attribute__((aligned(64)));
double B[N] __attribute__((aligned(64)));
double S[N] __attribute__((aligned(64)));
#pragma omp parallel for
#pragma vector aligned(A,B,C)
for(i=0;i<N;i+=1)
{
C[i]=0.0;
A[i]=1.0;
B[i]=0.1;
}
> OMP_NUM_THREADS=10 ./daxpy.x 1.000001
Go!
Done!
i=1000000000 j=10
T=15972239952 cycles, 5.70 sec, avg=11.33 sum=1.1e+09
42072.07051 MB/s
3506.00588 MFLOPS/s
> OMP_NUM_THREADS=12 ./daxpy.x 1.000001
Segmentation fault
Problèmes à la vectorisation
● Dépendance des données : attention à l’“aliasing” design,
pragma “hints”
● Alignement des données : aligner vos données, pragma “hints”
● Appel de fonctions dans les boucles : in-lining (aggressif: IPO)
Inlining (aggressif …)
double scale(double scale, double x) {
return(scale*x) ;};
void saxpy(float *A,float *B, float *C, const float
alpha, const int N)
{
int i;
for(i=0;i<N;i+=1)
C[i] = A[i]+scale(alpha,B[i]);
}
> icc -o main.x main.c scale.c saxpy.c -vec-report=3 -O2 -ipo
main.c(11): (col. 2) remark: LOOP WAS VECTORIZED.
main.c(11): (col. 2) remark: REMAINDER LOOP WAS VECTORIZED.
main.c(14): (col. 2) remark: loop was not vectorized: existence of vector dependence.
> icc -o main.x main.c scale.c saxpy.c -vec-report=3 -O2
main.c(14): (col. 2) remark: loop was not vectorized: existence of vector dependence.
saxpy.c(5): (col. 1) remark: routine skipped: no vectorization candidates.
scale.c
saxpy.c
movaps xmm1,XMMWORD PTR [rax*4+0x602360]
movaps xmm2,XMMWORD PTR [rax*4+0x602370]
movaps xmm3,XMMWORD PTR [rax*4+0x602380]
movaps xmm4,XMMWORD PTR [rax*4+0x602390]
mulps xmm1,xmm0
mulps xmm2,xmm0
mulps xmm3,xmm0
addps xmm1,XMMWORD PTR [rax*4+0x601260]
mulps xmm4,xmm0
addps xmm2,XMMWORD PTR [rax*4+0x601270]
addps xmm3,XMMWORD PTR [rax*4+0x601280]
addps xmm4,XMMWORD PTR [rax*4+0x601290]
movaps XMMWORD PTR [rax*4+0x603460],xmm1
movaps XMMWORD PTR [rax*4+0x603470],xmm2
movaps XMMWORD PTR [rax*4+0x603480],xmm3
movaps XMMWORD PTR [rax*4+0x603490],xmm4
add rax,0x10
cmp rax,0x3e0
jb 400600 <main+0x40>
Inlining …
icc -O2 -ip -c trigo.c -vec-report=2
trigo.c(4): (col. 2) remark: LOOP WAS VECTORIZED.
trigo.c(4): (col. 2) remark: loop skipped: multiversioned.
void trigo(float *A,float *B, float *C, const int N)
{
int i;
for(i=0;i<N;i+=1)
C[i] = cosf(A[i])+sinf(B[i]);
}
movaps xmm0, XMMWORD PTR [r13+rbp*4]
call __svml_cosf4
movaps xmm8, xmm0
movaps xmm0, XMMWORD PTR [r14+rbp*4]
call __svml_sinf4
addps xmm8, xmm0
movaps XMMWORD PTR [r15+rbp*4], xmm8
add rbp, 4
cmp rbp, rbx
jb ..B1.4
Short Vector Math Library
Problèmes à la vectorisation
● Dépendance des données : attention à l’“aliasing” design,
pragma “hints”
● Alignement des données : aligner vos données, pragma “hints”
● Appel de fonctions dans les boucles : in-lining (aggressif: IPO)
● Branches dans les boucles: créer plusieurs version de la boucle
en sortant la condition
● “Loop body too complex reports” : Design, Loop splitting …
● Stride non unitaire: design, gather (Haswell, Xeon Phi)
● “Vectorization seems inefficient reports” : Design, pragma
“hints”
● “Not inner loop” : unroll and jam, loop interchange …