26.06.2013 Views

Le calcul parallèle - Le CETMEF

Le calcul parallèle - Le CETMEF

Le calcul parallèle - Le CETMEF

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

Introduction<br />

<strong>Le</strong> <strong>calcul</strong> <strong>parallèle</strong><br />

Lorsque l'on met en place un système de <strong>calcul</strong> distribué ou un système de <strong>calcul</strong>s <strong>parallèle</strong>, le but est le<br />

même : la mise en commun de la puissance de <strong>calcul</strong> de plusieurs processeurs. <strong>Le</strong>s <strong>calcul</strong>s sont<br />

considérés comme <strong>parallèle</strong>s si les différents processeurs ont accès physiquement aux même données<br />

et effectuent les mêmes traitements. Alors que dans le cas de <strong>calcul</strong>s distribués, les données, mais<br />

également les traitements, peuvent être répartis sur différentes machines. Dans ce dernier cas, les<br />

communications, comme nous le verrons par la suite, jouent un rôle très important et influencent<br />

énormément les performances du <strong>calcul</strong>.<br />

Formellement, il y a peu de différences entre les <strong>calcul</strong>s <strong>parallèle</strong>s et les <strong>calcul</strong>s distribués. L'une d'entre<br />

elles est l'absence de communication explicite entre les processeurs dans le cas d'un <strong>calcul</strong> <strong>parallèle</strong>.<br />

<strong>Le</strong>s principales difficultés de mise en place sont les mêmes dans les deux cas. C'est pourquoi, par la<br />

suite, je ne ferai pas de différence entre les deux et j'emploierai le terme de processeur pour désigner<br />

une unité de <strong>calcul</strong> capable de communiquer avec les autres. Cette communication peut se faire par<br />

l'intermédiaire d'une mémoire partagée, par échanges de fichiers ou par envois de messages à l'aide<br />

d'un réseau.<br />

Il faut noter aussi qu'il n'y a pas de solution miracle. Il existe plusieurs solutions très différentes, aussi<br />

bien pour l'architecture matérielle que pour les algorithmes de <strong>calcul</strong>. Chaque solution répond à un<br />

problème particulier. Il faut donc bien étudier le problème afin de déterminer la solution qui va le mieux y<br />

répondre.<br />

Architecture matérielle<br />

Choix des processeurs<br />

Lorsque l'on met en place un système de <strong>calcul</strong>, on a le choix entre plusieurs types d'architecture<br />

processeur.<br />

La première est de prendre un petit nombre de processeurs mais qui ont une grande puissance de<br />

<strong>calcul</strong>, la deuxième de prendre un grand nombre de processeurs mais de faible puissance. Une dernière<br />

possibilité est de prendre un compromis entre les deux premières.<br />

<strong>Le</strong> choix de l'une de ces trois solutions est surtout une histoire de coût, les processeurs puissants étant<br />

plus chers. Il faut savoir aussi que plus le nombre de processeurs est élevé, plus le volume des<br />

communications nécessaires entre ces processeurs sera important, d'où une baisse d'efficacité.<br />

Choix d'une architecture mémoire<br />

Mémoire partagée<br />

<strong>Le</strong> premier type d'architecture mémoire pour une<br />

application distribuée est la mémoire partagée. Dans<br />

ce cas, plusieurs processeurs ont accès à la même<br />

mémoire physique. Ils peuvent opérer avec elle de<br />

manière indépendante et les changements fait par<br />

l'un des processeurs sont immédiatement visibles<br />

par les autres.<br />

Il existe deux types de mémoires partagées :<br />

• <strong>Le</strong>s mémoires à accès uniformes (UMA : Uniform Memory Access) où l'accès à la mémoire est le<br />

même pour chaque processeur, on parle alors d'accès équitable. Pour cela, il faut que les<br />

processeurs soient identiques. Il y a aussi, dans ce type d'architecture mémoire, ce que l'on<br />

appelle un contrôleur de cache (CC-UMA) qui s'assure que la mémoire cache de chaque<br />

processeur est cohérente avec les données présentes en mémoire centrale. Ce type de mémoire<br />

est présent dans la plupart des systèmes SMP (Symetric MultiProcessor).<br />

Conception d'un système à haute performance - <strong>Le</strong> <strong>calcul</strong> <strong>parallèle</strong> 1/6 Copyright © <strong>CETMEF</strong> 2004


• <strong>Le</strong>s mémoires à accès non uniformes (NUMA : Non Uniform Memory Access) où chaque<br />

processeur peut accéder à la mémoire indépendamment des autres. Ils peuvent donc être de<br />

types et de vitesses différentes. Comme pour l'architecture UMA, on peut y rajouter un système<br />

de cohérence de cache, mais l'implémentation de ce dernier est compliquée par ces accès non<br />

uniformes.<br />

Ce type de mémoire présente l'avantage de permettre un partage immédiat des données, facilitant la<br />

programmation. Mais cette solution coûte chère, ce qui limite le nombre de processeurs que l'on peut<br />

ajouter sur une même mémoire. De plus, les mécanismes de cohérence de cache sont coûteux en<br />

performance et plus on ajoute de processeurs, plus ce type de mécanisme devient indispensable. Si on<br />

ajoute le fait que le débit de la mémoire est limité, on voit bien que la hausse des performances ne suit<br />

pas linéairement le nombre des processeurs.<br />

Au niveau de la programmation, même si la communication est facilitée, il reste à la charge du<br />

programmeur de vérifier la cohérence des données en synchronisant les accès aux données critiques. Il<br />

faut éviter que deux processeurs puissent modifier une même variable sans tenir compte de la<br />

modification de l'autre processeur, sous peine de mettre en péril la cohérence des données et donc du<br />

résultat du <strong>calcul</strong>.<br />

Mémoire distribuée<br />

Dans ce cas, chaque processeur<br />

possède sa propre mémoire. La<br />

modification par l'un des<br />

processeurs de sa propre mémoire<br />

n'a pas d'influence directe sur celle<br />

des autres processeurs. Cela<br />

suppose donc de mettre en place<br />

une communication explicite entre<br />

les processeurs (souvent par<br />

l'intermédiaire d'un réseau).<br />

Ce type d'architecture présente<br />

l'avantage de permettre une<br />

hausse des performances<br />

processeurs / mémoires plus<br />

intéressante que dans le cas de la mémoire partagée, mais c'est au programmeur de gérer la plupart des<br />

détails de la communication entre les unités de <strong>calcul</strong>. Elle rend également difficiles les échanges<br />

complets de structures de données, pose des problèmes d'accès non uniformes dans le temps et elle<br />

rend la cohérence de données plus dure à maintenir. Mais elle présente l'intérêt de pouvoir s'agrandir<br />

facilement et on peut mettre ensemble des architectures processeurs et logiciels différentes si on le<br />

souhaite.<br />

Mémoire partagée et distribuée<br />

Ce dernier type de mémoire est un mélange des deux premiers. Dans cette architecture, il y a plusieurs<br />

groupes de processeurs partageant de la mémoire qui communiquent grâce à un réseau. Cela permet,<br />

dans une certaine mesure, de tirer les avantages de deux précédentes architectures et d'en réduire les<br />

inconvénients.<br />

Classification des systèmes <strong>parallèle</strong>s<br />

En 1966, Flynn a proposé une classification en quatre groupes des systèmes <strong>parallèle</strong>s :<br />

Single Instruction, Single Data (SISD)<br />

C'est dans cette catégorie que l'on retrouve les stations de travail traditionnelles. Elle correspond aux<br />

stations capables de ne traiter par cycle d'horloge qu'une instruction sur une donnée par cycle d'horloge.<br />

Ce n'est pas une architecture <strong>parallèle</strong>.<br />

Conception d'un système à haute performance - <strong>Le</strong> <strong>calcul</strong> <strong>parallèle</strong> 2/6 Copyright © <strong>CETMEF</strong> 2004


Single Instruction, Multiple Data (SIMD)<br />

Chaque processeur de cette architecture exécute la même instruction à chaque cycle d'horloge, mais les<br />

données traitées sont différentes. L'exécution est synchrone et déterministe sur chaque processeur.<br />

Multiple Instruction, Single Data (MISD)<br />

Il existe peu d'exemples de cette classe de systèmes <strong>parallèle</strong>s. Elle correspond aux systèmes capables<br />

d'exécuter plusieurs instructions sur la même données durant le même cycle d'horloge.<br />

Multiple Instruction, Multiple Data (MIMD)<br />

C'est dans cette catégorie que l'on trouve le plus de systèmes <strong>parallèle</strong>s. A chaque cycle d'horloge,<br />

chaque processeur exécute une instruction différente sur une donnée différente. Ces exécutions peuvent<br />

être synchrones ou non, déterministes ou non.<br />

Modèle de programmation <strong>parallèle</strong><br />

Il existe plusieurs modèles pour la programmation d'applications <strong>parallèle</strong>s. Ces modèles ne tiennent pas<br />

compte de l'architecture matérielle (processeurs et mémoires). Chacun d'entre eux peut être réalisé quel<br />

que soit le choix de l'architecture matérielle. Par exemple, il existe des méthodes pour partager une<br />

mémoire qui est physiquement distribuée et pour employer une méthode de communication par passage<br />

de message sur une architecture à mémoire partagée.<br />

Comme pour les architectures matérielles, il n'y a pas de meilleurs modèles, il y a juste des modèles qui<br />

répondent mieux à certains problèmes. Là encore, ce n'est qu'une étude au cas par cas qui permettra de<br />

décider quel modèle conviendra le mieux.<br />

Programmation par mémoire partagée<br />

Dans ce modèle les différentes tâches partagent le même adressage mémoire, elles peuvent lire et écrire<br />

dedans de manière indépendante et asynchrone. Cela permet de s'affranchir du problème de la<br />

communication des données entre les tâches. Mais le principal désavantage de ce modèle est que la<br />

cohérence des données et les accès concurrents doivent être gérés par le programmeur à l'aide de<br />

sémaphores ou de verrous, au risque de diminuer les performances du système.<br />

Programmation par threads<br />

<strong>Le</strong>s threads correspondent à des exécutions simultanées d'un même code qui peut, si besoin est, avoir<br />

des chemins d'exécution différents. Un thread est crée à la demande du programmeur, ce dernier peut<br />

donc faire exécuter en <strong>parallèle</strong> différentes fonctions de son application. <strong>Le</strong>s threads partagent le même<br />

espace mémoire, il faut donc veiller à s'assurer que deux threads ne modifient pas au même moment la<br />

valeur d'une même adresse globale. Ceci peut être fait en synchronisant les exécutions grâce à des<br />

verrous.<br />

Programmation par envoi de message<br />

Dans ce modèle, chaque processeur utilise sa propre mémoire locale. La communication des données et<br />

la synchronisation se font à l'aide de message dont le format est laissé à la discrétion du programmeur.<br />

<strong>Le</strong>s différentes instances de l'application répartie doivent être synchronisées, en effet, l'envoi d'un<br />

message doit faire l'objet d'une réception explicite par le destinataire.<br />

Programmation sur des données en <strong>parallèle</strong><br />

Dans ce modèle, les données sont découpées et distribuées vers les différentes unités de <strong>calcul</strong>. Ces<br />

dernières appliquent les même traitements aux données qui leur sont envoyées. Si on a une architecture<br />

à mémoire partagée, les différentes tâches accèdent aux données grâce à la mémoire globale, sinon,<br />

dans le cas de mémoires distribuées, les données sont transmissent à chaque unité qui les copie dans sa<br />

propre mémoire locale.<br />

Conception d'un système à haute performance - <strong>Le</strong> <strong>calcul</strong> <strong>parallèle</strong> 3/6 Copyright © <strong>CETMEF</strong> 2004


Autres modèles<br />

Hybride<br />

Dans ce modèle, plusieurs modèles de programmation présentés précédemment sont combinés. Par<br />

exemple, on peut citer l'utilisation simultanée de threads et de communications par passages de<br />

messages ou par de la mémoire partagée. Ce qui est souvent le cas pour les machines SMP mises en<br />

réseau.<br />

Single Program, Multiple Data (SPMD)<br />

Dans ce modèle, chaque tâche exécute le même jeu d'instructions. A chaque instant, les tâches peuvent<br />

être à des étapes différentes du programme. Elles n'exécutent pas forcément tout le programme dans<br />

son ensemble mais peuvent être limitées à certaines fonctions. <strong>Le</strong> programme doit donc prévoir ces cas<br />

de figure et agir en conséquence. De plus les données sur lesquelles travaillent les tâches ne sont pas<br />

forcément les mêmes. Ce modèle de haut niveau peut faire appel à plusieurs autres modèles plus<br />

simples décrits précédemment.<br />

Multiple Program, Multiple Data<br />

Ce modèle est le même que le modèle "Single Program, Multiple Data", sauf que chaque tâche peut<br />

exécuter un code différent et traiter des données différentes.<br />

Concevoir une application <strong>parallèle</strong><br />

Comprendre le problème ou le programme<br />

Afin de tirer les meilleures performances d'une application <strong>parallèle</strong>, il faut commencer par bien<br />

comprendre le problème, si on part de rien, ou le programme, si on part d'un programme existant.<br />

En effet, il faut tout d'abord déterminer si une exécution <strong>parallèle</strong> de l'application peut apporter un gain de<br />

performance. Pour cela, il faut qu'il y ait une certaine indépendance dans le traitement des données. Soit<br />

le traitement sur une partie des données est indépendant du traitement des autres, soit il existe des<br />

tâches qui peuvent être traitées indépendamment des autres. Par exemple, le <strong>calcul</strong> de la suite Fibonacci<br />

( F(k+2) = F(k+1) + K(k), chaque élément est égal à la somme des deux précédents) ne peut pas être<br />

distribué à cause de la trop grande dépendance des <strong>calcul</strong>s. Il faut attendre le <strong>calcul</strong> des deux termes<br />

précédent pour pouvoir <strong>calcul</strong>er le terme courant.<br />

Il faut aussi trouver les parties les plus gourmandes en terme de temps de <strong>calcul</strong> de l'application. En<br />

effet, c'est sur ces parties que le travail de mise en <strong>parallèle</strong> sera le plus efficace en terme de<br />

performance.<br />

<strong>Le</strong> partitionnement<br />

<strong>Le</strong> partitionnement est la première étape d'écriture d'un programme <strong>parallèle</strong> ou distribué. Il consiste à<br />

découper le problème, soit en terme de données indépendantes, soit en terme de fonctionnalités. C'est<br />

de ce partitionnement que va dépendre le gain apporté par la mise en <strong>parallèle</strong> du <strong>calcul</strong>. Chaque partie<br />

du <strong>calcul</strong> considérée comme indépendante pourra alors être exécutée par un processeur différent.<br />

<strong>Le</strong>s communications<br />

<strong>Le</strong>s communications entre les processeurs dépendent beaucoup de la tâche à effectuer. En effet, lorsque<br />

le traitement d'une donnée est complètement indépendant des autres, il n'est pas nécessaire de mettre<br />

en place un système de communication. Malheureusement la grande majorité des <strong>calcul</strong>s, pour être<br />

mené à bien, ont besoin d'une partie des résultats des autres processeurs voisins. Il est donc nécessaire<br />

de diffuser les résultats vers ceux qui en ont besoin.<br />

<strong>Le</strong>s communications jouent un rôle très important dans les performances globales d'un système<br />

distribué. Il y a plusieurs points à considérer :<br />

• <strong>Le</strong> coût des communications : il y a toujours un surcoût engendré par les communications, lorsque<br />

l'application envoie et reçoit des messages mais aussi lors qu'elle attend un message en provenance<br />

Conception d'un système à haute performance - <strong>Le</strong> <strong>calcul</strong> <strong>parallèle</strong> 4/6 Copyright © <strong>CETMEF</strong> 2004


d'un autre processeur. Durant ce traitement ou cette attente, le processeur n'est pas à la disposition<br />

de l'application et donc du <strong>calcul</strong>.<br />

• La latence et le débit du réseau de communication sont aussi des points importants. La latence est<br />

le temps nécessaire pour envoyer un message de taille minimal (0 octet) sur le réseau, le débit est la<br />

quantité de données que le réseau peut transmettre par unité de temps. C'est de ces deux<br />

paramètres que va dépendre la vitesse des communications (si on ne tient pas compte du traitement<br />

du message par le système d'exploitation). Il est en général préférable de ne pas envoyer beaucoup<br />

de petits messages, en effet chaque envoi nécessite un temps de traitement minimum qui n'est plus<br />

négligeable par rapport au temps nécessaire à l'émission de petits messages. Pour remédier à cela,<br />

on peut, par exemple, regrouper les petits messages pour limiter cet effet en formant un message de<br />

taille plus importante.<br />

• <strong>Le</strong>s communications peuvent être synchrones ou non, bloquantes ou non. <strong>Le</strong>s communications<br />

synchrones sont plus lentes et sont généralement bloquantes, c'est à dire que les deux processeurs<br />

engagés dans la communication doivent attendre la fin de la communication pour continuer. Par<br />

contre les communications asynchrones sont la plupart du temps non bloquantes. Quand un<br />

processeur veut envoyer un message à un autre, il envoie le message et peut immédiatement<br />

reprendre le cours de son exécution sans se soucier de quand l'autre processeur recevra le<br />

message. C'est là le principal avantage des communications asynchrones.<br />

<strong>Le</strong>s synchronisations<br />

Il existe différents types de synchronisations :<br />

• <strong>Le</strong>s barrières impliquent l'ensemble des tâches de l'application. Pour passer une barrière, il faut que<br />

toutes les tâches de l'application aient atteint cette barrière.<br />

• <strong>Le</strong>s verrous et sémaphores impliquent un nombre quelconque de tâches. Ils sont utilisés<br />

principalement pour protéger une donnée ou une section critique du code. Lorsqu'une tâche obtient<br />

le verrou, le système doit garantir qu'elle est la seule à l'avoir et qu'elle peut, en toute sécurité,<br />

accéder à la donnée ou rentrer dans la section critique. <strong>Le</strong>s autres tâches cherchant à obtenir ce<br />

verrou pendant ce temps seront bloquées en attente du verrou.<br />

• <strong>Le</strong>s communications synchrones peuvent aussi jouer le rôle d'élément de synchronisation.<br />

La dépendance des données<br />

Il y a dépendance entre deux parties d'une application lorsque l'exécution de l'une affecte le résultat de<br />

l'autre. Une dépendance de donnée entraîne une utilisation de la valeur d'une même variable par des<br />

tâches différentes. Ces dépendances sont très importantes en ce qui concerne les applications <strong>parallèle</strong>s<br />

puisque c'est l'un des principaux freins au développement d'applications <strong>parallèle</strong>s.<br />

Ces dépendances peuvent être gérées soient par la communication des données entre les processeurs<br />

lors de points de synchronisation (dans le cas de mémoires distribuées) soit par la synchronisation des<br />

lectures / écritures (dans le cas de mémoires partagées).<br />

La répartition de charge<br />

<strong>Le</strong> but de la répartition de charge est d'utiliser au maximum les ressources disponibles. Dans le cas idéal,<br />

toutes les tâches sont occupées en permanence.<br />

Cette répartition des tâches joue également un rôle important dans les performances globales du<br />

système. Surtout dans le cas où il nécessaire de mettre en place des synchronisations, en effet, c'est la<br />

tâche la plus lente qui va déterminer la performance de l'ensemble, les tâches les plus rapides devant<br />

attendre les plus lentes.<br />

Cette répartition peut être statique si la vitesse des processeurs et le temps de <strong>calcul</strong> sont connus. Mais,<br />

dans le cas contraire, on peut mettre en place une répartition dynamique, par exemple les tâches, qui une<br />

fois qu'elles ont fini leurs traitements, viennent demander de nouvelles données.<br />

La granularité<br />

La granularité représente le volume de traitement qui il y a entre deux communications (ou<br />

synchronisations). C'est une mesure qualitative du ratio entre le volume de <strong>calcul</strong> et le volume des<br />

communications.<br />

Une granularité faible signifie que les périodes de <strong>calcul</strong> sont relativement courtes par rapport au période<br />

Conception d'un système à haute performance - <strong>Le</strong> <strong>calcul</strong> <strong>parallèle</strong> 5/6 Copyright © <strong>CETMEF</strong> 2004


communication. Elle facilite la répartition de charge mais entraîne un surcoût important de communication<br />

et laisse peu d'opportunités d'augmenter les performances.<br />

Une granularité forte, à l'inverse, signifie qu'il y a relativement peu de communication en comparaison<br />

avec les périodes de <strong>calcul</strong>. Cela laisse plus d'opportunités d'augmenter les performances mais la<br />

répartition de la charge est beaucoup moins évidente à mettre en place.<br />

<strong>Le</strong>s limites des applications <strong>parallèle</strong>s<br />

La loi d'Amdahl montre que le gain maximum que l'on peut avoir lors que l'on développe une application<br />

<strong>parallèle</strong> par rapport à la même application mais non <strong>parallèle</strong> est fonction du pourcentage du code qu'il<br />

est possible de rendre <strong>parallèle</strong>.<br />

Soit P le pourcentage de code parallélisable, le gain maximum est 1 / ( 1 - P )<br />

Soit N le nombre de processeurs et S le pourcentage de code non <strong>parallèle</strong>, le gain est 1 / ( P/N + S)<br />

On voit bien que plus on ajoute de processeurs, plus le gain sera élevé. Mais ceci n'est que la limite<br />

théorique, elle ne prend pas en compte les problèmes soulevés par la mise en <strong>parallèle</strong> des <strong>calcul</strong>s<br />

(communications, synchronisations, dépendances, ...).<br />

De plus, les applications <strong>parallèle</strong>s sont plus complexes à développer. En effet, les exécutions<br />

simultanées et les flots de données entre ces exécutions ne sont pas des plus faciles à appréhender.<br />

<strong>Le</strong> choix de la technique dépend donc fortement du <strong>calcul</strong> à effectuer en fonction des propriétés de ce<br />

dernier. Il faut trouver un compromis entre la communication entre les sites qui est souvent coûteuse et la<br />

taille des données du <strong>calcul</strong>.<br />

<strong>Le</strong>s facteurs limitant la vitesse d'un <strong>calcul</strong> <strong>parallèle</strong> sont :<br />

• <strong>Le</strong> démarrage de la tâche<br />

• <strong>Le</strong>s synchronisations<br />

• <strong>Le</strong>s communications des données<br />

• <strong>Le</strong>s limites fixées par le compilateur, les bibliothèques, les outils, le système d'exploitation.<br />

Conclusion<br />

Comme je l'ai dit à plusieurs reprises, la conception d'un système de <strong>calcul</strong> <strong>parallèle</strong> et distribué est très<br />

fortement influencée par la ou les applications que devra faire fonctionner ce système. Mais une fois que<br />

l'architecture matérielle a été choisie, il convient également de bien choisir les algorithmes qui seront<br />

utilisés. En effet, certains mécanismes sont plus efficaces sur des architectures bien particulières.<br />

Par exemple, une application programmée à l'aide de threads sera plus adaptée sur des processeurs à<br />

mémoire partagée puisque les threads partagent le même espace mémoire.<br />

Si le volume des données est trop important pour tenir dans la mémoire d'un des processeurs, il est plus<br />

judicieux, si cela est possible, de la répartir sur les différents processeurs du système. Dans ce cas, les<br />

systèmes à base de mémoire distribuée seront à privilégier pour mieux répartir le volume des données<br />

entre les noeuds.<br />

Par contre, il est intéressant de noter, que si l'on souhaite lancer une même application sur des volumes<br />

limités de données différentes, les deux architectures peuvent convenir.<br />

Donc, si le système de <strong>calcul</strong> est amené à faire fonctionner un panel d'applications diverses, il peut être<br />

préférable de mélanger les deux types d'architecture mémoire et d'optimiser l'équilibrage de charge (et<br />

donc la répartition de la charge de travail) sur les processeurs les plus adaptés à la tâche.<br />

Conception d'un système à haute performance - <strong>Le</strong> <strong>calcul</strong> <strong>parallèle</strong> 6/6 Copyright © <strong>CETMEF</strong> 2004

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!