7. Métriques de performance pour algorithmes et programmes parallèles 7.1 Introduction : le temps d’exécution suffit-il? Algorithme/programme parallèle ⇒ Utilisation de plusieurs processeurs ⇒ Réduction du temps d’exécution , ⇒ Augmentation du coût de la machine / L’algorithme parallèle peut utiliser une mise en oeuvre qui augmente la quantité totale de travail effectuée / Donc, non, le temps d’exécution ne suffit pas! Dans ce qui suit : • Métriques asymptotiques, pour des algorithmes • Métriques réelles, pour des programmes 7.2 Temps d’exécution et profondeur Définition 1 Le temps d’exécution TP (n) d’un algorithme parallèle A exécuté pour un problème de taille n est le temps requis pour exécuter l’algorithme en supposant qu’un nombre illimité de processeurs sont disponibles pour exécuter les diverses opérations. Définition 2 La profondeur d’un calcul est définie comme la longueur de la plus longue chaîne de dépendances présentes dans ce calcul. La profondeur représente le meilleur temps d’exécution possible en supposant une machine idéale. Nb. d’UE Temps min. 1 2 3 4 ... 9 6 6 6 ... Figure 7.1: Graphe de dépendances pour calcul d’une racine d’un polynome de deuxième degré pour illustrer la notion du meilleur temps parallèle comme longueur du plus long chemin. Figure 7.2: Graphe de dépendances pour calcul de la somme d’un tableau de huit éléments, pour illustrer la notion du meilleur temps parallèle comme longueur du plus long chemin. Il s’agit de métriques asymptotiques (idéales). En pratique, on mesurera le temps d’exécution réel avec un nombre limité de processeurs 7.3 7.3.1 Coût, travail et optimalité Coût Définition 3 Soit un algorithme A utilisé pour résoudre, de façon parallèle, un problème de taille n en temps TP (n). Soit p(n) le nombre maximum de processeurs effectivement requis par l’algorithme. Le coût de A est alors C(n) = p(n) × TP (n) 7.3.2 Travail Définition 4 Le travail, W (n), dénote le nombre total d’opérations effectuées par l’algorithme parallèle pour un problème de taille n sur une machine à p(n) processeurs. 7.3.3 Coût vs. travail Le travail est un estimé plus précis du nombre total exact d’opérations exécutées Relation entre temps, travail et coût • Séquentiel : TS (n) = W (n) = C(n) • Parallèle : TP (n) ≤ W (n) ≤ C(n) Figure 7.3: Le travail. Figure 7.4: Le temps. Figure 7.5: Le nombre de processeurs. Figure 7.6: Le coût. Soit le graphe suivant, qui représente les opérations et leurs dépendances pour faire la somme d’un tableau v comptant n éléments : 1. Quel est le temps d’exécution? 2. Quel est le travail? 3. Quel est le nombre maximal de processeurs requis? 4. Quel est le coût? Exercice 7.1: Temps vs. coût vs. travail. Solution : 1. Temps : O(lg n) 2. Travail : O(n) 3. Nb. processeurs : O(n) 4. Coût : O(n lg n) Autre différence entre coût et travail : le travail est une métrique compositionnelle, pas le coût. Soit S1 et S2 deux segments de code. Travail(S1; S2) = Travail(S1) + Travail(S2) Coût(S1; S2) ≥ Coût(S1) + Coût(S2) Exemple illustrant la non-compositionalité procedure foo( ref int a[*], int b[*], int n ) { co [i = 1 to n] a[i] = ... # Expression O(1) oc co [j = 1 to lg(n)] proc( a, n ) oc } procedure proc( ref int a[*], int n ) { for [i = 1 to n] { a[i] = ... # Expression O(1) } } Algorithme 7.1: Petit algorithme pour illustrer que le coût n’est pas une métrique compositionnelle. Quel est le temps de cet algorithme? Quel est son travail? Quel est son coût? 7.3.4 Optimalité Définition 5 Un algorithme parallèle est dit optimal en travail si le travail W (n) effectué par l’algorithme est tel que W (n) ∈ Θ(TS∗(n)). Définition 6 Un algorithme parallèle est dit optimal en coût si le coût C(n) de l’algorithme est tel que C(n) ∈ Θ(TS∗(n)). Question : L’algorithme avec le graphe de l’exercice précédent est-il optimal en travail? En coût? 7.4 7.4.1 Accélération et efficacité Accélération relative vs. absolue Détermine combien fois un programme parallèle est plus rapide qu’un programme séquentiel. Notons TP (p, n) le temps pour un problème de taille n avec p processeurs — TP (n) = TP (+∞, n). Notons TS∗(n) le temps requis pour le meilleur programme séquentiel (pour taille n). Définition 7 L’accélération relative Arp : Arp TP (1, n) = TP (p, n) Définition 8 L’accélération absolue Aap : Aap TS∗(n) = TP (p, n) Soit une machine avec p processeurs. Quelle est en théorie la meilleure accélération absolue qu’il est possible d’obtenir? Aap TS∗(n) ≤ = TP (p, n) ? Exercice 7.2: Meilleure accélération pour une machine avec p processeurs. Solution : En théorie, la meilleure accélération est de p pour p processeurs. Accélération linéaire vs. superlinéaire On parle d’accélération linéaire lorsque Ap = p. En pratique, des accélérations linéaires sont rares / Bizarrement, on rencontre parfois des accélérations superlinéaires, où Ap > p. 7.4.2 Efficacité de l’utilisation des processeurs L’efficacité nous indique dans quelle mesure les divers processeurs sont utilisés ou non. Définition 9 L’ efficacité d’un programme est le rapport entre le temps d’exécution séquentiel et le coût d’exécution sur une machine à p processeurs : TS∗(n) Aap(n) TS∗(n) Ep = = = C(n) p p × TP (p, n) 7.5 Dimensionnement (scalability) Un algorithme est dimensionnable lorsque le niveau de parallélisme augmente au moins de façon linéaire avec la taille du problème. Une architecture est dimensionnable si la machine continue à fournir les mêmes performances par processeur lorsque l’on accroît le nombre de processeurs. 7.6 Coûts des communications Un algorithme est dit distribué lorsqu’il est conçu pour être exécuté sur une architecture composée d’un certain nombre de processeurs — reliés par un réseau, où chaque processeur exécute un ou plusieurs processus —, et que les processus coopèrent strictement en s’échangeant des messages. Dans un tel algorithme, il arrive fréquemment que le temps d’exécution soit largement dominé par le temps requis pour effectuer les communications entre processeurs En d’autres mots, les communications deviennent les opérations «barométriques». 7.7 Sources des surcoûts d’exécution parallèle • Création des threads et changements de contexte • Synchronisation et communication entre les threads • Communications inter-processeurs • Répartition de la charge de travail (load balancing) • Calculs supplémentaires 7.8 Lois d’Amdhal, de Gustafson–Barsis et fraction séquentielle expérimentale Remarques : • La section qui suit est (en partie) une traduction et (en partie) une adaptation du chapitre intitulé «Performance analysis» du livre de M.J. Quinn «Parallel Programming in C with MPI and OpenMP » • Parmi les modifications, certains éléments de notation ont été simplifiés ou modifiés : Notation de Quinn Notation utilisée Temps σ(n) σ partie séquentielle Temps ϕ(n) ϕ partie parallèle Accélérationψ(n, p) ψ Temps T (n, p) pour taille n et p processeurs T (p, n) 7.8.1 Temps d’exécution, partie séquentielle, partie parallèle et accélération Le temps pour un problème de taille n avec p processeurs peut être décomposé en deux parties : • σ = partie intrinsèquement séquentielle • ϕ = partie pouvant s’exécuter en parallèle Temps pour un processeur : T (1, n) = σ + ϕ (7.1) Temps pour p processeurs : ϕ T (p, n) = σ + p Accélération : σ+ϕ ψ ≤ σ + ϕ/p (7.2) 7.8.2 Loi d’Amdahl f = fraction des opérations qui doivent être exécutées de façon séquentielle (0 ≤ f ≤ 1) : σ f = σ+ϕ On a alors : σ σ+ϕ = f Et : σ 1 ϕ = − σ = σ( − 1) f f Donc : σ+ϕ ψ ≤ σ + ϕ/p σ/f = σ + σ(1/f − 1)/p 1/f = 1 + (1/f − 1)/p 1 = f + (1 − f )/p Définition 10 (Loi d’Amdahl) Soit f la fraction des opérations qui doivent être exécutées de façon séquentielle, avec 0 ≤ f ≤ 1. Alors, l’accélération maximale ψ pouvant être obtenue avec p processeurs est : 1 ψ ≤ f + (1 − f )/p Exemples : • Un programme pour lequel 90 % pourrait s’exécuter en parallèle. Accélération maximale avec huit (8) processeurs : ψ≤ 1 ≈ 4.7 0.1 + (1 − 0.1)/8 • Un programme pour lequel 75 % pourrait s’exécuter en parallèle. Accélération maximale sur une machine parallèle sans limite de processeurs : 1 lim ≈4 p→∞ 0.25 + (1 − 0.25)/p Limites de la loi d’Amdahl La formulation de la loi d’Amdahl ignore les surcoûts associés à l’exécution parallèle, c’est-à-dire, les coûts des synchronisations et communications entre processus. ϕ(n) + κ(n, p) T (p, n) = σ(n) + p L’effet Amdahl Règle générale, la complexité de κ(n, p) est plus faible que celle de ϕ(n). C’est-à-dire : le temps d’exécution augmente plus rapidement que le temps de synchronisation et communication. Donc, pour un nombre fixe de processeurs, l’accélération va augmenter lorsqu’on augmente la taille du problème. En d’autres mots, plus la taille du problème est grande, plus l’accélération est grande. 7.8.3 Loi de Gustafson–Barsis La loi d’Amdahl suppose que l’objectif est de produire le résultat le plus rapidement possible. Mais, souvent, l’objectif est plutôt de traiter un problème de plus grande taille Pour la loi de Gustafson–Barsis, on va supposer que le temps d’exécution parallèle est fixe. L’effet Amdahl s’explique par le fait que lorsqu’on augmente la taille du problème, la fraction du temps d’exécution de la partie intrinsèquement séquentielle diminue. On ajoute des processeurs ⇒ On peut traiter des problèmes plus gros ⇒ La fraction parallélisable augmente ⇒ La fraction séquentielle diminue ⇒ L’accélération augmente s = fraction du temps d’exécution parallèle consacrée aux opérations séquentielles : σ s = σ + ϕ/p On a alors : σ = (σ + ϕ/p)s Et : ϕ = (σ + ϕ/p)(1 − s)p Donc : ψ ≤ = = = = σ+ϕ σ + ϕ/p (σ + ϕ/p)(s + (1 − s)p) σ + ϕ/p s + (1 − s)p s + p − sp p + (1 − p)s Définition 11 (Loi de Gustafson–Barsis) Pour un problème de taille n traité avec p processeurs, soit s la fraction du temps d’exécution (parallèle) consacrée à la partie intrinsèquement séquentielle (avec 0 ≤ s ≤ 1). Alors, l’accélération maximale ψ pouvant être obtenue est la suivante : ψ ≤ p + (1 − p)s L’accélération prédite par la loi de Gustafson–Barsis est aussi appelée scaled speedup Exemple : • Un programme s’exécute en 220 secondes sur 64 processeurs. Des mesures montrent que 5 % du temps d’exécution est pour des parties séquentielles. Quelle sera l’accélération (scaled speedup) obtenue par ce programme? ψ = 64 + (1 − 64)(0.05) = 64 − 3.15 = 60.85 7.8.4 Accélération selon la loi d’Amdhal vs. selon la loi de Gustafson-Barsis Nb. Amdhal Gustafson Amdhal Gustafson Amdhal Gustafson procs. f ou s 0,05 0,05 0,10 0,10 0,20 0,20 2 1,90 1,95 1,82 1,90 1,67 1,80 4 3,48 3,85 3,08 3,70 2,50 3,40 8 5,93 7,65 4,71 7,30 3,33 6,60 16 9,14 15,25 6,40 14,50 4,00 13,00 32 12,55 30,45 7,80 28,90 4,44 25,80 64 15,42 60,85 8,77 57,70 4,71 51,40 128 17,41 121,65 9,34 115,30 4,85 102,60 Tableau 7.1: Accélération maximale possible pour diverses valeurs de f ou s et divers nombres de processeurs. Une autre façon de voir ces deux lois : • Loi d’Amdhal – Borne inférieure de l’accélération – Hypothèse pessimiste = la fraction séquentielle reste constante, peu importe la taille du problème = Le temps intrinséquement séquentiel augmente au même rythme que la taille du problème / • Gustafson-Barsis – Borne supérieure de l’accélération – Hypothèse optimiste = le temps intrinsèquement séquentiel reste constant, peu importe la taille du problème • La réalité. . . est quelque part entre les deux – Le temps intrinsèquement séquentiel augmente, mais beaucoup plus lentement que la taille du problème – ⇒ si on augmente la taille du problème, alors on pourra augmenter le nombre de processeurs pour obtenir une meilleure accélération , • Accélération Amdhal avec 4 processeurs – 10 / (1 + (9/4)) = 10 / (1 + 2.25) = 3.08 • Accélération Gustafson–Barsis avec 4 processeurs – (1 + 4*9) / 10 = 37 / 10 = 3.7 7.8.5 Fraction séquentielle déterminée expérimentalement 7.A Mesures de performances : Un exemple concret public static int nbDansCercleSeq ( int nbLancers ) { Random rnd = new Random (); int nb = 0; for ( int k = 0; k < nbLancers ; k ++ ) { double x = rnd . nextDouble (); double y = rnd . nextDouble (); if ( x * x + y * y <= 1.0 ) { nb += 1; } } return nb ; } Programme Java 7.1 Une fonction parallèle evaluerPi (et sa fonction auxiliaire nbDansCercleSeq) pour approximer la valeur de π à l’aide de la méthode de Monte Carlo à plusieurs threads. @SuppressWarnings ( " unchecked " ) public static double evaluerPi ( final int nbLancers , int nbThreads ) { ExecutorService pool = Executors . newFixedThreadPool ( nbThreads ); Future < Integer > lesNbs [] = new Future [ nbThreads ]; / / u n c h for ( int k = 0; k < nbThreads ; k ++ ) { lesNbs [ k ] = pool . submit ( () -> { return nbDansCercleSeq ( nbLancers / nbThreads ); } ); } int nbTotalDansCercle = 0; for ( int k = 0; k < nbThreads ; k ++ ) { try { nbTotalDansCercle += lesNbs [ k ]. get (); } catch ( Exception ie ) {} } pool . shutdown (); return 4.0 * nbTotalDansCercle / nbLancers ; } Figure 7.7: Graphe donnant le temps d’exécution du programme Pi.java pour différents nombres de threads. Les temps sont indiqués pour trois (3) séries différentes d’exécution. Figure 7.8: Graphe d’accélération du programme Pi.java pour différents nombres de threads— moyenne de trois (3) éxécutions. Figure 7.9: Graphe d’efficacité du programme Pi.java pour différents nombres de threads— moyenne de trois (3) éxécutions, pour huit (8) processeurs.