Département Informatique L’optimisation Laurent JEANPIERRE IUT de CAEN – Campus 3
Département Informatique 2 Contenu du cours Introduction Le Pentium 4 Conseils généraux Optimiser le code assembleur Conseils généraux Conseils spécifiques
Département Informatique 3 L’optimisation En théorie ? Rendre un programme optimal ! En pratique ? Augmenter sa vitesse d’exécution Réduire son empreinte mémoire Rarement compatibles…
Département Informatique 4 Principes de base Règle fondamentale : D’abord un programme qui marche Ensuite un programme optimisé TESTER après chaque optimisation ! Langages de haut niveau Faciles à utiliser Développement rapide Surcouche importante Pas optimisé
Département Informatique 5 L’optimisation aujourd’hui Un programme classique « optimisé », c’est : 90 % de haut niveau (plusieurs millions lignes) Interface utilisateur, sauvegarde, … Java, SmallTalk, Delphi, C++, … 9 % de bas niveau ( lignes) Routines souvent utilisées, … Maîtrise nécessaire (processeur, mémoire) Pascal, C, … 1 % d’assembleur (<200 lignes) Routines critiques (temps exécution) Exécutées des millions de fois (boucles)
Département Informatique 6 Dans ce cours… Focus sur le Pentium 4 Disponible en salle machine Machines courantes aujourd’hui Principes restent valables Pour d’autres processeurs Pour d’autres compilateurs Dans certaines limites
Département Informatique 7 Contenu du cours Introduction Le Pentium 4 Conseils généraux Optimiser le code assembleur Conseils généraux Conseils spécifiques
Département Informatique 8 Le Pentium 4 Processeur CISC à cœur RISC Instructions CISC opérations RISC ( op) Architecture netburst : Pipeline long (30) Architecture super-scalaire Plusieurs unités parallèles Processeur de nouvelle génération Optimise le code à la volée
Département Informatique 9 Architecture P4 ( © Intel )
Département Informatique 10 Architecture P4 (1) Le cache L2 : 256Ko, données + instructions L1 : 8Ko données 2 cache-miss avant remplissage du cache Accès en ordre croissant uniquement Trace : 12K op (≈60K ?) Le Front-End (Pré-)charge les instructions Décode les instructions chargées (1/cycle) Cache les op dans le trace-cache Fournit les op aux unités de calcul (3/cycle)
Département Informatique 11 Architecture P4 (2) Retirement Unit « Mise à la retraite » Finalise les op Valide les données des registres Dans l’ordre du programme original Voir l’unité d’exécution… Jusqu’à 3 op/cycle
Département Informatique 12 Architecture P4 (3) Le prédicateur de branchements Prédit si un saut sera suivi ou non Au moment de son décodage Avant son exécution ! Statique : au décodage Saut Jmp/Call : exécuté Saut en arrière : exécuté Saut en avant : non exécuté Dynamique : si dans le trace-cache Selon les exécutions précédentes Statistiques sommaires... et en nombre limité (4096 pour les sauts, 16 pour les RET )
Département Informatique 13 Architecture P4 (4) Unité d’exécution ( © Intel ) 4 ports en parallèle, out of order
Département Informatique 14 Architecture P4 (5) Unité d’exécution (2) 4 ports en parallèle : Chaque port est indépendant 1 cycle pour le transfert de données de port à port ≠ Sauf de Load aux autres Chaque port est « pipeliné » Exécution de plusieurs instructions en séquence Granularité variable ½ cycle pour les ALU 2 cycles pour les FPU/SIMD PAS DU TOUT pour les divisions, ni FPU avancé (√,…)
Département Informatique 15 Architecture P4 (6) Unité d’exécution (3) Les données en mémoire Port 2 pour le chargement Crée un nouveau registre si besoin Port 3 pour le stockage Mémoire lente… Registres virtuels : Le P3 a 40 registres gen., le P4 en a 120 ! Un Load ou une opération ALU Alloue un registre Permet l’exécution hors-ordre si besoin
Département Informatique 16 Architecture P4 (7) Unité d’exécution (4) Utilise des buffers locaux 48 en lecture 24 + en écriture (selon versions) « Store forwarding » : copie de buffer à buffer sans passer par la mémoire ! « Write combining » : écriture de paquets en mémoire (ex. : 1*64 au lieu de 8*8) Store Forwarding, règles du jeu On ne lit pas plus que ce qu’on a écrit On aligne les lectures et les écritures Il faut écrire AVANT de lire
Département Informatique 17 Contenu du cours Introduction Le Pentium 4 Conseils généraux Optimiser le code assembleur Conseils généraux Conseils spécifiques
Département Informatique 18 Conseils généraux D’abord améliorer les algorithmes Ex. : Quick-sort au lieu du tri à bulles. Utiliser les données les plus petites Si la précision le permet ! float au lieu de double char au lieu de int (éventuellement) traitement plus rapide parallélisation possible (SIMD)
Département Informatique 19 Conseils généraux (2) Eviter les variables globales Accès relatif (8b ou 16b au lieu de 32b) Meilleure localité (cache + efficace) Eviter les écritures multiples en // Seulement 4 buffers pour le Write- Combining Eviter de jouer avec les pointeurs Préférer les tableaux Compilation optimisée plus facile
Département Informatique 20 Conseils généraux (3) Eviter les branches Le programme se suit naturellement Pas de branchement pas d’erreur de prédiction « Inliner » les fonctions courtes (évite bouger pile) Aligner variables Accès plus rapide Accès séquentiel cache optimisé
Département Informatique 21 Conseils généraux (4) Eviter les dépendances longues Autoriser le parallélisme Exemple : eax v1*v2*v3*v4*v5*v6*v7*v8 movlv1,%eax #1 imullv2,%eax #14/8 imullv3,%eax #14/8 imullv4,%eax #14/8 imullv5,%eax #14/8 imullv6,%eax #14/8 imullv7,%eax #14/8 imullv8,%eax #14/8 1+7*14=99 cycles movlv1,%eax #1#0-1 imullv2,%eax #14/8#1-15 (0) movlv3,%ebx #1 #2-3 imullv4,%ebx #14/8#3-17 (1) movlv5,%ecx #1 #4-5 imullv6,%ecx #14/8#9-23 (0) movlv7,%edx #1 #5-6 imullv8,%edx #14/8#11-25(1) imull%ebx,%eax #14/4.5#17-31(0) imull%ecx,%edx #14/4.5#25-39(1) mov$0,%edx #0.5#6-7 !!!! 39 cycles
Département Informatique 22 Contenu du cours Introduction Le Pentium 4 Conseils généraux Optimiser le code assembleur Conseils généraux Conseils spécifiques
Département Informatique 23 Optimiser le code assembleur… Soit écrire le code de toute pièce Soit optimiser le travail fait par un compilateur Souvent spécifique à un processeur particulier… Tout à refaire si le programme change !
Département Informatique 24 Contenu du cours Introduction Le Pentium 4 Conseils généraux Optimiser le code assembleur Conseils généraux Conseils spécifiques
Département Informatique 25 Conseils généraux Préférer les instructions simples Consulter les tables du fabricant Ex : LOOP : 8 cycles (+ besoin ROM microcode) SUB $-1 + JNZ : 1+1 ops, 1 cycle ! IMUL $3 : 14 cycles (+ besoin ROM microcode) 3*ADD : 1.5 cycles ! Eviter registres xH Crée de fausses dépendances Impose 1 op de plus (Shift interne) Eviter les instructions INC/DEC Drapeau C intouché Crée dépendance
Département Informatique 26 Conseils généraux (2) Passer les paramètres par registre Evite le stockage en mémoire Dérouler boucles (quoique ?) ≤ 16 itérations prédiction de branchement meilleure Sauf si taille > Trace-cache ! Utiliser LEA pour calculs simples Sauf échelle (trop lent) Ex : LEA 15(%ebx,%ecx),%eax eax ebx+ecx+15 Utiliser XOR au lieu de MOV $0 Plus rapide, code plus court Casse la dépendance aussi (SAUF XORP !)
Département Informatique 27 Conseils généraux (3) Ne pas mélanger code et données Panique du cache Aligner les sauts sur 16 octets Surtout pour le Pentium-M Ne pas créer le cadre pile si inutile Récupère registre EBP Instructions en moins Placer les invariants en mémoire Libère un registre Efficace grâce au cache + load-buffer
Département Informatique 28 Contenu du cours Introduction Le Pentium 4 Conseils généraux Optimiser le code assembleur Conseils généraux Conseils spécifiques
Département Informatique 29 Conseils spécifiques Préférer le SSE2 aux FPU Si beaucoup de calculs Plus rapide (même unité de calcul) Possibilité de traitement parallèle Sauf si instructions spécifiques (trigo, …)
Département Informatique 30 Conseils spécifiques SSE : Aligner les données Accès mémoire plus rapide Ajouter du bourrage ! Plusieurs tableaux tableau de structure Parallélisation plus simple FPU : Opérations avec des entiers 16b (PAS 32b) Préférable de charger l’entier + opération flottante
Département Informatique 31 Exemple: Rotation 2D en C Algorithme intuitif #define N 4096 #define A M_PI/4. struct point {double x,y;}; struct point pts[N]={{1,1},{-1,1}, {-1,-1},{1,-1}}; struct point res[N]; int main() { int i,j; double ca = cos(A); double sa = sin(A); for (j=0;j<500000;j++) // Pour mesurer des choses for (i=0; i<N; i++) { res[i].x = pts[i].x*ca – pts[i].y*sa; res[i].y = pts[i].x*sa + pts[i].y*ca; }// for i } Normal : 21s Optimisé O3:12s
Département Informatique 32 Exemple: Rotation 2D en C Sans les structures #define N 4096 #define A M_PI/4. double px[N]={1,-1,-1,1}; double py[N]={1,1,-1,-1}; double rx[N]={1,-1,-1,1}; Double ry[N]={1,1,-1,-1}; int main() { int i,j; double ca = cos(A); double sa = sin(A); for (j=0;j<500000;j++) // Pour mesurer des choses for (i=0; i<N; i++) { rx[i] = px[i]*ca – py[i]*sa; ry[i] = px[i]*sa + py[i]*ca; }// for i } Normal : 14s Optimisé O3:9s
Département Informatique 33 Exemple: Rotation 2D en C Algorithme intuitif, double float #define N 4096 #define A M_PI/4. struct point {float x,y;}; struct point pts[N]={{1,1},{-1,1}, {-1,-1},{1,-1}}; struct point res[N]; int main() { int i,j; float ca = cos(A); float sa = sin(A); for (j=0;j<500000;j++) // Pour mesurer des choses for (i=0; i<N; i++) { res[i].x = pts[i].x*ca – pts[i].y*sa; res[i].y = pts[i].x*sa + pts[i].y*ca; }// for i } Normal : 14s Optimisé O3:9s
Département Informatique 34 Exemple: Rotation 2D en C Sans structure, double float #define N 4096 #define A M_PI/4. float px[N]={1,-1,-1,1}; float py[N]={1,1,-1,-1}; float rx[N]={1,-1,-1,1}; float ry[N]={1,1,-1,-1}; int main() { int i,j; float ca = cos(A); float sa = sin(A); for (j=0;j<500000;j++) // Pour mesurer des choses for (i=0; i<N; i++) { rx[i] = px[i]*ca – py[i]*sa; ry[i] = px[i]*sa + py[i]*ca; }// for i } Normal : 14s Optimisé O3:9s
Département Informatique 35 Exemple: Rotation 2D en SSE Algorithme intuitif.data.align 16 N=1024 px:.rept N.double 1,-1,-1,1.endr py:.rept N.double 1,1,-1,-1.endr rx:.space 8*4*N ry:.space 8*4*N ca:.double 0,0 sa:.double 0,0 format:.string "%f, %f\n" Quatre:.short 4 off=py-px.text.global main main: pushl %ebp fldpi fidivsQuatre fsincos fstlca fstplca+8 fstlsa fstplsa+8 movapsca,%xmm4 movapssa,%xmm5
Département Informatique 36 Exemple: Rotation 2D en SSE Algorithme intuitif (suite) movl$500000,%ebx boucle3: movl$(2*N),%ecx movl$px,%esi movl$rx,%edi boucle: movapd(%esi),%xmm0 movapdoff(%esi),%xmm1 movapd(%esi),%xmm2 movapdoff(%esi),%xmm3 mulpd%xmm5,%xmm0# x.sin a mulpd%xmm5,%xmm1# y.sin a mulpd%xmm4,%xmm2# x.cos a mulpd%xmm4,%xmm3# y.cos a subpd%xmm1,%xmm2 addpd%xmm0,%xmm3 movapd%xmm2,(%edi) movapd%xmm3,off(%edi) addl$16,%esi addl$16,%edi loopboucle subl$1,%ebx jnzboucle3 Temps : 4,9s
Département Informatique 37 Exemple: Rotation 2D en SSE Algorithme optimisé (cœur – 1) movl$500000,%ebx movl$(-32*N),%ecx.align16 boucle3: boucle: movapdrx(%ecx),%xmm1# 1u port 2, 7/10-7 movapdpy(%ecx),%xmm0# 1u port 2, 7/11-8 movapdpy(%ecx),%xmm2# 1u port 2, 7/12-9 movapdrx(%ecx),%xmm3# 1u port 2, 7/13-10 movapdpy+16(%ecx),%xmm4# 1u port 2, 7/14-11 movapdrx+16(%ecx),%xmm5# 1u port 2, 7/15-12 movapdpy+16(%ecx),%xmm6# 1u port 2, 7/16-13 mulpdsa,%xmm1 # y.sin a# 1u port 1, 7/ movapdrx+16(%ecx),%xmm7# 1u port 2, 7/18-15 mulpdca,%xmm2 # x.cos a# 1u port 1, 7/ mulpdsa,%xmm0 # x.sin a# 1u port 1, 7/ mulpdca,%xmm3 # y.cos a# 1u port 1, 7/
Département Informatique 38 Exemple: Rotation 2D en SSE Algorithme optimisé (cœur – 2) movapd%xmm2,ry(%ecx)# 2u port 0, 7/ movapd%xmm3,ca(%ecx)# 2u port 0, 7/ subpd%xmm5,%xmm6# rx# 1u port 1, 4/ addpd%xmm4,%xmm7# ry# 1u port 1, 4/ movapd%xmm6,ry+16(%ecx)# 2u port 0, 7/ movapd%xmm7,ca+16(%ecx)# 2u port 0, 7/ nop# 1u port 0/1, addl$32,%ecx# 1u port 0/1, #----- # 30u jnzboucle# 1u port 0, ?/ nop# 1u port 0/1, nop# 1u port 0/1, movl$(-32*N),%ecx# 1u port 0/1, subl$1,%ebx# 1u port 0/1, 0.5/ jnzboucle3# 1u port 0(branch), ?/0.5
Département Informatique 39 Exemple: Rotation 2D en SSE Algorithme optimisé (cœur – 3) jnzboucle# 1u port 0, ?/0.5 nop# 1u port 0/1, 0.5 movl$(-32*N),%ecx# 1u port 0/1, 0.5 subl$1,%ebx# 1u port 0/1, 0.5 jnzboucle3# 1u port 0, ?/0.5 #---- # 6u Temps : 4,5s XMM0XMM3XMM1XMM2XMM4XMM7XMM5XMM6 x0|x1y0|y1 x0|x1x1|x2y1|y2 x1|x2 *sin*cos*sin*cos*sin*cos*sin*cos +-+- ry0|ry1rx0|rx1ry1|ry2rx1|rx2
Département Informatique 40 Exemple: Rotation 2D en SSE Algorithme optimisé floats Chaque registre contient 4 float ou 2 double Meilleur parallélisme ! ca contient 4 fois le cosinus sa contient 4 fois le sinus Temps : 2,25s XMM0XMM3XMM1XMM2XMM4XMM7XMM5XMM6 x0..x3y0..y3 x0...x3x4..x7y4..y7 x4..x7 *sin*cos*sin*cos*sin*cos*sin*cos +-+- ry0..ry3rx0..rx3ry4..ry7rx4..rx7
Département Informatique 41 Bilan & Conclusion Le programme optimisé est Plus rapide (14 secondes 2,25 secondes) Plus long (31 lignes de C 120 lignes d’asm.) Ecriture du programme C 1 heure Ecriture du programme Assembleur 3-4 heures Optimisation du programme Plusieurs dizaines d’heures Mais un programmeur expérimenté irait plus vite… certainement…