• Aucun résultat trouvé

.

4.1 Introduction

Au cours de ces dernières années les processeurs sont devenus de plus en plus complexes (plusieurs niveaux de caches, de mémoires, de registres...). Cette augmentation de la complexité fait que l’étude de performance et l’optimisation sont de plus en plus complexes et difficiles à comprendre, en effet les processeurs actuels sont devenus multicœurs et même récemment many-coeurs. Les différences qui existent entre les architectures résident dans la gravure, les différents niveaux de cache et les opérations vectorielles disponibles, ce qui permettra aux constructeurs de conserver la loi Moore en augmentant le nombre de cœurs dans les processeurs tout en augmentant les tailles des registres d’instructions pour réaliser de plus en plus d’opérations vectorielles. Ainsi la puissance de calcul devient très importante. La mémoire aussi ne cesse de s’améliorer en augmentant la fréquence ce qui implique également une augmentation de la bande passante. Ainsi les codes de calculs doivent être adaptés à ces architectures pour tirer profit de leurs performances. Cela implique des grands efforts pour développer des codes extrêmement parallèles, composés de plusieurs niveaux d’optimisation (programmation pour mémoire partagée, mémoire distribuée, vectorisation, cache blocking....).

La difficulté majeure de la plupart des concepteurs d’applications scientifiques réside dans la caractéri-sation des applications sur les architectures sur lesquelles elle sont développées. Il s’agit de connaître les performances que nous pouvons atteindre avec une architecture donnée, en prenant en compte toutes les composantes présentes et utilisées pour exécuter une application donnée. Le code de calcul étant associé à une ou plusieurs machines, il faut également être capable de connaître les besoins en terme d’opérations flottantes et en terme de mémoires nécessaires pour le bon fonctionnement de l’appli-cation. L’objectif est de réunir tous ces ingrédients dans un modèle de performance simple et facile d’utilisation.

Pour tirer profit des architectures parallèles, nous procédons en général une programmation avec des langages par passage de messages pour les plate-formes à mémoire distribuée (MPI par exemple), et avec des langages basés sur la programmation multi-threadé pour la mémoire partagée (pragmas

OpenMP par exemple). Avant chaque parallélisation nous réalisons une étude détaillée sur le temps

d’exécution des différentes phases du calcul, nous repérons ainsi les parties les plus consommatrices en termes de temps de calcul et nous procédons à leurs parallélisations.

Pour faire évaluer la parallélisation d’une application, nous procédons au calcul de l’efficacité qui se définit comme suit :

en utilisant un nombre p de cœurs identiques, avec t1 le temps d’exécution du code avec 1 seul cœur

de calcul et tp avec p cœurs de calcul, l’efficacité du parallélisme dans ce cas est donnée par :

E(P ) = t1

p tp (4.1)

L’efficacité est en général inférieure à 1, quand cette valeur s’approchera de 1, on dit que l’ap-plication est "efficace". Dans le cas où l’ensemble des fonctions dans un code ne peuvent pas être parallélisées, cette valeur décroit très rapidement avec le nombre de processus utilisés, ce phénomène est connu sous le nom de la loi d’Amdahl.

La loi d’Amdahl [6] a été annoncée en 1967, les deux principales composantes de cette loi sont l’effica-cité et l’accélération. En effet si s est la portion séquentielle dans le code, alors l’accélération théorique du temps de calcul par rapport à une exécution séquentielle est définie par :

A = 1

s +1−sp , (4.2)

avec p le nombre de processus utilisés lors de la parallélisation, A représente l’accélération du temps de calcul sur p processeurs par rapport à une exécution séquentielle. Ce qui conduit à une efficacité parallèle limité par :

E(p)≤ 1

La loi d’Amdahl montre que tout algorithme possédant une partie séquentielle, verra son passage à l’échelle sévèrement limité, même si cette partie est représentée par une petite portion du temps total d’exécution du code.

Cette définition permettra d’effectuer plusieurs types d’études d’efficacité d’un code parallèle, parmi lesquelles on peut citer :

— Scalabilité forte : on fixe la taille du problème en augmentant le nombre de processus, et on calcule le rapport entre le temps de calcul de la version parallèle et la version séquentielle, ce rapport et appelé speedup. On trace une courbe entre les valeurs théoriques et les valeurs calculées des speedup, si les deux sont proches nous avons une scalabilité forte parfaite, sinon nous essayons de trouver les blocages pour améliorer la scalabilité.

— Scalabilité faible (loi de Gustafson [49]) : on augmente la taille du problème avec le nombre des processeurs, nous avons ainsi une taille de calcul constante par processeur. Si le temps de calcul est constant, nous avons ainsi une scalabilité faible parfaite, nous augmentons ainsi le nombre de processeurs pour résoudre des problèmes de plus grandes tailles.

Ces études de performances nous permettent d’évoluer la parallélisation des codes et leur efficacité par rapport à une version séquentielle, néanmoins elles sont insuffisantes pour l’évaluation des perfor-mances inatteignables par rapport aux perforperfor-mances théoriques d’une architecture donnée en tenant en compte par exemple la fréquence d’horloge des processeurs ainsi que les bandes passantes théoriques des différentes niveaux de mémoire que contient l’architecture.

Le goulot d’étranglement causé par la bande passante de chaque niveau de mémoire, doit être introduit dans les paramètres du modèle de performance. Il existe plusieurs modèles permettant une prédiction de performances sur des machines multi-coeurs avec une grande précision. On peut citer par exemple les modèles analytiques statistiques [37], des études basées sur le comportement de diffèrent niveaux de cache [92], ou aussi un modèle appelé Execution Cache Memory mode (ECM) [91]. Tous ces outils permettent une simulation robuste des performances qui vont des hauts niveaux aux niveaux très bas de la mémoire. L’inconvénient majeur de ces techniques, réside dans la difficulté de leur implémenta-tion, de leur compréhension par des scientifiques, qui ne sont pas spécialistes dans les architectures des calculateurs modernes. Donc trouver un outil simple et facile à comprendre serait donc d’une plus grande valeur.

Le modèle de Roofline promet de répondre à ces critères. Ce modèle simple développé initialement par Samuel Williams à Berkeley [101] permet de réaliser une étude simple des performances d’une application donnée sur une architecture donnée. Il s’appuie sur le nombre d’opérations flottantes et des transferts de données dans un code donné comparées aux performances théoriques de la machine pour identifier les performances de l’application et la possibilité ou non d’une optimisation compte-tenu de l’état actuel de l’application et des caractéristiques de la machine.

La version originale du roofline ne traite que le comportement global de la mémoire et elle est en fonc-tion de la bande passant de la mémoire RAM via une étude quantitative où les variables sont calculées à la main. Une version plus élaborée qui traite les effets des caches dans le roofline, a été introduite par Ilic et al [54]. Dans cette thèse nous allons coupler et améliorer ces deux versions pour caractériser l’application Advection 2D développé lors de cette thèse. Nous allons ainsi réaliser :

— Une étude des performances avec le Roofline original et le CARM ;

— le développement d’outils fiables et automatiques sans instrumentation de codes ;

— l’introduction de deux nouvelles versions du Roofline : une version en fonction du temps et une version en fonction des blocs ;

— l’introduction de la nouvelle version du CARM introduite dans les outils de profilage Intel Advisor de Intel.

4.2 Roofline scalaire

4.2.1 Le modèle Roofline

L’observation clé qui a conduit à la réalisation de ce modèle, est que la bande passante du transfert de données entre les processeurs et la mémoire est souvent le facteur limitant de la performance, c’est ce qu’on appelle le goulot d’étranglement de la mémoire "bottleneck". Dans ce modèle la bande passante mémoire étudiée est celle est celle entre la mémoire DRAM et le dernier niveau de caches (LLC), c’est ce qu’on appelle off-ship memory c’est-à-dire la mémoire qui n’appartenant pas à la puce contenant le processeur et ces mémoires caches.

Ce modèle permet de donner un aperçu global de l’exécution d’une application donnée sur une machine donnée. Il permet de répondre à des questions primordiales avant de commencer l’optimisation et la parallélisation d’une application :

— L’application s’exécute-elle de façon optimale sur cette machine ?

— Qu-est-ce qui limite la performance ? Est-ce-que mon application est limitée par la mémoire (Memory bound) ou par le calcul (cpu Bound) ?

— Par quoi commencer pour optimiser mon application et tirer profits des composants de la machine à disposition ?

— comment peut-on augmenter les performances et à quel prix ?

Le "Roofline model" consiste à calculer l’intensité arithmétique d’un code (AI) qui représente le nombre d’opérations par octets du trafic de la DRAM vers le dernier niveau de cache divisé par le nombre d’opérations flottantes.

Ce modèle permet de réaliser une étude simple et intéressante des performances du code, notamment il caractérise la performance d’une application par Flops/bytes, ce qui va nous permettre au cours des optimisations de savoir si notre code est CPU Bound ou Memory Bound.

Pour commencer l’optimisation d’une application il est nécessaire de déterminer la puissance théorique maximale de la machine et le pic-maximal atteignable (en considérant l’ensemble (application + ma-chine) qui dépend du nombre d’opérations, des accès mémoire et de la bande passante de la machine. Pour avoir une idée de la puissance de l’application à l’état actuel et la puissance maximale qu’on peut atteindre.

Voici les règles générales pour calculer les grandeurs nécessaires pour tracer le Roofline :

Peak Flop : nombre maximal de Flops que l’on peut effectuer avec une machine donnée ;

Peak Flop (Flops/s) = F r × Nbnode× Nbinstr× Nbcore. (4.4)

Avec :

— Fr : la fréquence d’un cœur de calcul (Mhz) (cycles/seconde) ;

— Nbnode : Le nombre de nœuds de calcul dans une machine donnée ;

— Nbinstr : Le nombre d’opérations flottantes que nous pouvons réaliser par cycle d’horloge, selon le type de registres présents dans la machine. Par exemple, pour les registre AVX (256 bits), nous pouvons réaliser 8 opérations par cycle d’horloge en simple précision et 4 en double précision. Sachant qu’avec un processeur Intel équipé des registres AVX nous pouvons réaliser deux

instruc-tions de calcul par cycle d’horloge (voir 2.14)), ce qui nous permet ainsi de réaliser 8 opération

en double précision et 16 opérations en simple précision.

— Nbcore : Le nombre de cœurs disponibles par nœud de calcul

Par exemple, pour une machine constituée de deux processeurs ivy bridge avec 4 coeurs et une fréquence d’horloge de 3.3 GHz nous avons les Peak Flops suivants :

— Peak Flop pour les opérations en double précision : 4 × 3.3 × 2 × 8 = 211.2 Gflops/s — Peak Flop pour les opérations en simple précision : 4 × 3.3 × 2 × 16 = 422.4 Gflops/s

Peak BW : la bande passante de la mémoire, elle se calcule comme suit :

P eakBW (bytes/s) = F rRAM × Nbdt× Nbbus× Nbinterf ace, (4.5)

avec :

— F rRAM : la fréquence d’horloge de la mémoire ;

— Nbdt : le nombre de données transférées par cycle d’horloge. (2 par cycle d’horloge dans les

mémoires actuelles de type DDR) ;

— Nbbus : la longueur de bus, dans les mémoires de type DDR, 64 bits ;

— Nbinterf ace : le nombre de canaux utilisés (nombre de carte mémoires utilisées).

Pour une machine constituée de mémoires de type DDR2-800, avec une fréquence d’horloge de 400 Mhz et deux canaux, la bande passante est :

400,000,000 clocks/second × 2 lignes/clock × 64 bits × 2 interfaces = 12.8 GB/s Performances du code

Les quantités utilisées pour caractériser la performance d’une application seront calculées à la main ou en utilisant les outils de profilage (que nous détaillerons par la suite). Le modèle Roofline introduit une nouvelle métrique pour caractériser une application appelée : intensité arithmétique (AI), qui est le rapport entre :

— F : Le nombre d’opérations Flottantes dans le code (en Flops).

— B : le nombre de données transférées entre la RAM et le dernier niveau de cache (en Bytes) Ainsi la performance maximale atteignable pour une application donnée, et sur une machine donnée qui est défini par :

M axP = min(P eakF lops, P eakBW× AI) (4.6)

Le nombre d’opérations flottantes dans une application peut être calculé à la main si la boucle la plus consommatrice est constituée uniquement des opérations de multiplications et additions. Si l’ap-plication est constituées de plusieurs fonctions constituées d’opérations mathématiques compliquées, dont on ignore le nombre d’opérations flottantes, nous devons calculer le nombre de flops par des outils de profilage.

Pour le calcul de Bytes nous devons utiliser des outils de profiling ou les compteurs hardware. En résumé,pour tracer le Roofline les quantités suivantes doivent être mesurées :

Pour l’application • Le temps d’exécution ; • le trafic mémoire ;

• le nombre opérations flottantes. Pour la machine

• Peak Flops théorique.

• Pic bande passante (parfois on utilise le résultat du benchmark STREAM )

Le modèle du Roofline combine l’intensité (AI) sur l’axe des x (en log2) avec la rendement (Gflops/s) sur l’axe y (en log2) (voir4.1).

Figure 4.1 – Le modèle Roofline

À partir de cette présentation simplifiée des performances de notre application, nous pourrons sa-voir si notre application est Memory bound, c’est-à-dire limitée par la mémoire, ou notre application est

CPU bound c’est-à-dire limitée par le calcul. Nous pouvons aussi représenter plusieurs niveaux de Peak

Flops maximum atteignable de l’application en fonction des niveaux d’optimisations dans l’application.

Prenons par exemple le cas d’une machine composée de deux sockets Broadwell, composée de quatre cœurs chacun, de fréquence d’horloge de 4 GHz et d’une Mémoire DDR3-2400 sur 8 canaux. Nous avons alors une bande passante maximale de 153 GB/s et un peak Flops en simple précision de 4096 Gflos/s et en double précision de 2048 Gflops/s. Ces pics sont calculés en tenant en compte l’utilisation des opérations FMA sur des registres AVX512 ainsi nous pouvons calculer et tracer plusieurs Roofline en fonction des niveaux de vectorisation (registres présent dans la machine) dans le code. La figure représente dans le même graphique les différents niveaux de Roofline en fonction des optimisations dans le code en double précision.

Figure 4.2 – Le modèle Roofline : représentation en fonction des registres de vectorisation

Une même présentation en fonction de plusieurs niveaux de mémoire peut être réalisée.

notre graphique pour suivre l’évolution de l’optimisation et prévoir les limites liées à notre application et à la machine utilisée.

La Figure4.3illustre l’exemple d’une modélisation de performances, les points en bleu représentent les

performances de l’application, les points en noir correspondent aux performances maximales que nous pouvons atteindre avec une application donnée et sur une architecture donnée. Prenons l’exemple du point bleu dans la partie memory bound, nous constatons que notre application est memory bound et nous pouvons effectuer des optimisations pour s’approcher du point noir qui correspond à la perfor-mance maximale, une fois ce point est atteint nous ne pouvons plus effectuer d’optimisation. Dans ce cas là, pour tirer profit de la puissance de notre architecture nous devons réaliser des modifications dans le code, notamment rajouter des opérations ou revoir l’algorithme implémenté dans l’application afin d’augmenter l’intensité arithmétique et en déplaçant ainsi le point représentant les performances (point bleu) de l’application vers la droite du Roofline, et augmentant ainsi les performances maximales que nous pouvons atteindre avec le nouvel algorithme, ce qui nous laisse une marge d’optimisation.

Figure 4.3 – Modèle Roofline : représentation des performances actuelles de deux versions d’une application, en bleu les performance actuelles et en noir les performances maximales atteignables sur une machine donnée

4.2.2 Le modèle : Cache Aware Roofline Model

À fin d’utiliser des performances des unités arithmétiques proches de leurs pics, quelques données chargées à partir de la mémoire peuvent être utilisées pour effectuer plusieurs opérations, cela est possible grâce à la mémoire cache qui est maintenant présente dans tous les processeurs modernes. Cette zone de mémoire à une taille petite, mais une faible latence et une bande passante très élevée, toutes les données chargées en mémoire sont enregistrées dans le cache, donc il serait nécessaire de les garder dans cette mémoire pour faire le plus grand nombre de opérations possibles.

Une version modifiée du Roofline a été développée par Aleksandar Ilic et al [54], que nous avons eu l’occasion d’inviter au cours de cette thèse dans le cadre du projet Icode. Cette nouvelle version du

Roofline inclut tous les niveaux de mémoire, contrairement à la version originale du Roofline qui inclut

juste la bande passante entre le DRAM et le dernier niveau de cache. La figure ci-dessous montre le fonctionnement de Roofline clasique :

Figure 4.4 – Modèle Roofline classique

Dans la figure4.4nous avons deux boites, une boite rouge qui correspond à la mémoire DRAM et

une boite bleu qui correspond à un ensemble constitué des CPUS et de tous les niveaux de cache qui seront vus comme une boite noire. Le nombre de données transférées depuis et vers la mémoire sera calculé entre la DRAM et le dernier niveau de cache sans prendre en compte les transactions depuis et entre les autres niveaux.

Une autre remarque qui a conduit à la réalisation du CARM, est que dans la représentation du Roofline il existe un seuil où une application passe de la partie Memory Boud à la partie CPU bound pour lequel on peut atteindre les performances maximales de la machine en terme d’opérations flottantes. Si par exemple une application rentre dans le niveau de cache L2 ceci améliorera par conséquent la bande passante entre le niveau de cache L2 et le cœur et va donc changer la limite entre la région Memory bound et la région CPU bound, ce qui implique une représentation des bandes passantes pour chaque niveau de mémoire.

Les différences majeures entre le Roofline scalaire et le CARM sont les suivantes :

— le calcul des transferts mémoire introduit toutes les opérations mémoires, incluant les différents niveaux de cache

— la bande passante dépend du niveau de mémoire et elle est reliée directement aux cœurs des processeurs.

Le modèle propose donc le trafic complet de la mémoire ce qui rend maintenant le AI (l’intensité

arithmétique) dépendante de tous les niveaux de cache dans un seul graphique. La figure 4.5 illustre

les différents niveaux de caches et les différentes bandes passantes qui sont toutes calculées vers les cœurs contrairement au modèle classique où la bande passante est calculée entre la DRAM et le dernier niveau de cache.

Figure 4.5 – modèle CARM

La difficulté majeure pour tracer le CARM réside maintenant dans le calcul des bandes passantes

théoriques entre les cœurs et les différents niveaux de mémoire, sachant que la bande passante BL1−>C

est facilement calculable, en fonction de la fréquence des cœurs, du nombre de load et de store qu’offre le cache et la taille de la ligne de cache. Elle sera calculée comme suit :

BL1−>C = F r× Nbtransf ert× cachesize (4.7)

avec F r la fréquence d’horloge du cœur, Nbtransf ertle nombre de load et de store que nous pouvons

effectuer par cycle d’horloge et cachesizela taille de la ligne de cache. Prenons l’exemple d’un processeur avec une fréquence d’horloge de 3.5GHz, avec un cache qui permet 2 loads et un store de taille 128 bits, la bande passante entre le coeur et le cache L1 est donc de 16octets×3.5×3GHz = 168Go/s par coeur. L’auteur de CARM a développé un benchmark qui permettra de calculer les autres bandes passantes nécessaires pour la construction du CARM, il s’agit de BL2−>c, BL3−>C, BDRAM−>C. La figure ci-dessous représente les résultats de calcul de bandes passantes atteignables sur une machine Intel i7 3770K composée d’un processeur de 4 cœurs, de fréquence d’horloge de 3.5 Ghz et avec deux cartes

mémoires RAM de fréquence de 800 Mhz, ce qui donne une bande passante BD−>LLC = 25.6Go/set

Documents relatifs