• Aucun résultat trouvé

2.3 Présentation des architectures programmables

3.1.1 Méthode d’analyse des algorithmes

Méthodologie générale La méthode permettant d’obtenir précisément les opérations réalisées par l’exé- cution d’un algorithme donné repose sur l’analyse dynamique du graphe d’exécution. Ce graphe est généré à partir de son exécution, dans notre cas, il correspond à l’enchaînement des opérations exécutées. Le jeu de données de référence est composé des images de la base de données Kodak [Kodak Eastman Co. 1995], ainsi que d’images issues de prises de vues du monde réel dans différentes conditions pour s’assurer des résultats réalistes. Le profil dynamique est réalisé avec l’aide de la chaîne d’outilsMAsSque nous avons conçue et dé- veloppée pour construire et analyser le graphe d’exécution à partir de l’exécution réelle d’un programme mais aussi des informations sur les ressources utilisées (opérations, accès mémoire, registres etc.).

Description de la chaîne d’outilsMAsS

Algorithme C/C++ Algorithme mixte C/C++ et assembleur MAsS Moteur MAsS MAsS Définition des instructions Programme C/C++ compilable et profilable gcc + libs Rapport de profilage Graphe d’exécution Analyse MAsS

Détail des ressources Extraction du Parallélisme

Exécution

MAsS

FIG. 3.1: MÉTHODE DE GÉNÉRATION DU GRAPHE

D’EXÉCUTION ET D’OBTENTION DES RÉSULTATS DE

PROFILAGE À PARTIR D’UN ALGORITHME DEMAsS.

Pour profiler l’exécution d’un programme, ins- truction après instruction, chacune d’entre elles est décrite et un comportement lui est associé. Cela re- vient à obtenir un langage au niveau instructions, au- trement dit un assembleur à partir duquel nous dé- crivons les algorithmes à étudier. Nous avons choisi de définir un jeu d’instructions élémentaires réduit, de typeRISC. Le jeu d’instructions choisi peut aussi bien être celui d’un processeur existant – ce qui per- met d’utiliser un compilateur – qu’un jeu d’instruc- tions entièrement défini.

Afin de ne pas imposer de contrainte liée à un mo- dèle architectural existant, nous avons choisi d’implé- menter notre propre langage assembleur. De cette ma- nière, il est aussi possible d’évaluer l’impact de l’uti- lisation d’extensions au jeu d’instructions initial sur l’exécution d’un programme. Il s’agit par exemple de l’utilisation de gestionnaires d’adresses, d’extensions vectorielles, ou encore d’intervenir sur la dynamique des opérations (8 bits, 16 bits, 64 bits etc.). Le code à analyser peut être inséré en ligne dans un programme ou une fonction pré-existanteC/C++, de manière à ci- bler les fonctions à analyser.

3.1. Analyse des algorithmes 45

L’algorithme ainsi décrit est intégré puis ensuite analysé parMAsSqui génère un programmeCcompilable. Comme c’est le cas pour gprof [McKusick 1982], l’exécution du programme compilé reste fonctionnellement équivalente à la version originale. Il intègre un ensemble de primitives qui permet de collecter des statistiques sur son exécution et de générer le graphe d’exécution. La Figure3.1présente le flot qui permet d’obtenir les résultats complets du profilage d’un programme.

L’analyse du graphe d’exécution est réalisée par un dernier outil de MAsS. Il permet d’extraire des sous- graphes, le parallélisme au niveau instructions, ou générer un graphe pour l’affichage. La chaîne d’outilsMAsS

est écrite en langage Practical Extraction and Report Language (Perl) [Wall 200] et en langageC. La mise en forme des graphes est assurée par la suite d’outils Graphviz [Gansner 1999].

« Rôle » des instructions Afin d’obtenir un profil fiable, nous avons choisi de marquer les instructions en fonction de leur tâche assignée dans le programme. Pour cela, trois « rôles » ont été définis. Le premier est destiné à identifier les instructions associées aux accès aux données, c’est à dire le calcul des adresses mémoire et le chargement ou l’écriture de valeurs. Le second « rôle » permet de marquer les instructions associées au contrôle du programme. Il s’agit notamment de celles permettant la gestion des boucles traitant le parcours de l’image et la gestion des indices. Enfin, le troisième groupe marque les instructions qui contribuent directement au résultat de l’algorithme.

3.1.1.1 Définition de la méthode d’analyse et d’obtention des résultats

A partir du programme décrit à l’aide du langage assembleur que nous avons défini, associé au modèle comportemental des instructions utilisées, un programme C compilable est généré. Il intègre les primitives permettant de collecter des statistiques sur les instructions exécutées et leur enchaînement. Celles-ci vont du nombre d’accès réalisés à la file de registres, aux opérateurs utilisés en passant par les erreurs de dépassement de capacité reportées.

Dynamique des calculs La dynamique des opérateurs de calcul est obtenue expérimentalement. Pour cela, nous faisons varier leur taille jusqu’à ce qu’un dépassement de capacité soit reporté, ou jusqu’à ce que l’erreur obtenue sur les images traitées dépasse un seuil donné. Cette erreur est calculée en utilisant la somme de la différence pixel à pixel au carré de l’image de référence avec l’image traitée. Pour faire varier la taille des opérateurs, il suffit de paramétrer le fichier de configuration deMAsS.

Construction du graphe d’exécution Le graphe d’exécution est construit à partir des différentes res- sources auxquelles l’exécution du programme a fait appel. Il correspond à l’enchaînement de toutes les opéra- tions et des résultats de calculs intermédiaires. Pour cela, chaque ressource à laquelle le programme fait appel est associée à un nœud du graphe d’exécution (registre, opérateur, constante etc.). Le « rôle » de chaque nœud est renseigné afin de pouvoir identifier ultérieurement la nature de l’utilisation des ressources associées.

Pour expliciter au mieux le parallélisme du programme et pour ne pas dépendre des ressources définies (nombre de registres, nombre d’opérateurs etc.), le graphe d’exécution est construit en renommant systéma- tiquement les registres et les opérateurs. La méthode employée pour renommer ces différents éléments est illustrée dans l’exemple de la Figure3.2. Cet exemple présente une fonction linéaire simple y= a × x + b pour

laquelle les registres et les opérateurs sont suffixés du numéro correspondant à leur appel. La lecture du graphe permet de visualiser les données manipulées ainsi que les opérateurs en jeu, avec le parallélisme exploitable.

Y = A × x + B = (0) R1 (0) = (1) R2 (0) = (2) R1 (1) * (0) R1 (2) + (0) R1 (3) A x B R1 ← A R2 ← x R1 ← R1 × R2 R2 ← B R1 ← R1 + R2

FIG. 3.2:TRADUCTION DE LA FONCTIONy= a × x + bEN OPÉRATIONS ÉLÉMENTAIRES PUIS EN GRAPHE D’EXÉ-

CUTION.

Conclusion Les fonctions à identifier doivent être décrites dans un langage prédéfini au niveau instruc- tions.MAsSpermet ensuite de générer une version compilable qui intègre les primitives permettant d’obtenir des statistiques précises sur les appels des instructions, l’accès aux registres et les données manipulées, ainsi que le graphe d’exécution complet du programme. Afin d’obtenir des résultats réalistes, le programme doit être exécuter sur différents jeux de données réelles. Chacune de ces exécutions fait intervenir plusieurs millions ou milliards d’accès aux opérateurs. Or, à chacun de ces appels correspond un nœud du graphe d’exécution. Il est donc essentiel de définir des stratégies permettant de réaliser une analyse efficace de ces graphes particulière- ment volumineux.

3.1.1.2 Définition de la méthode d’analyse du graphe

Le graphe d’exécution est construit à partir des ressources auxquelles le programme a fait appel durant son exécution. Par conséquent, il peut contenir plusieurs milliards d’occurrences. Ce graphe ainsi constitué contient intrinsèquement les différentes formes de parallélisme ainsi que le décompte et l’enchaînement des différents opérateurs. Afin d’extraire efficacement ces informations, différentes méthodes d’analyse doivent être mises en œuvre.

La première étape consiste à segmenter le graphe d’exécution en extrayant des sous-graphes. Pour cela, il est par exemple possible de se baser sur des attributs associés aux opérations, en les regroupant en fonction de leurs « rôles » (accès aux données, contrôle, calcul du résultat). Pour chacun des sous-graphes obtenus, nous recherchons les suites d’opérations similaires, ce qui conduit à étendre le jeu d’instructions initialement défini au niveau de MAsS. Dans ce cas, le programme doit à être à nouveau décrit en tenant compte de ces extensions, puis analysé à nouveau pour déterminer l’impact de son utilisation sur l’exécution du programme (nombre d’opérations et de registres nécessaires, etc.). Cet impact est obtenu par comparaison des décomptes d’opérations (un poids peut être associé aux instructions en fonction de leur éventuel coût architectural). Pour cela, il suffit de décompter les nœuds qui correspondent à des opérations, il s’agit des nœuds de forme carrée dans la Figure3.2. Enfin, ce graphe met en évidence différentes formes de parallélisme.

Extraction des formes de parallélisme Dans un premier temps, nous cherchons à identifier le parallélisme de tâches. Il s’agit des tâches qui peuvent être réalisées simultanément à d’autres, par exemple en les enchaî- nant. Généralement, une partie de ce parallélisme de tâches a été identifiée manuellement lors de l’écriture du programme. Les « rôles » attribués aux opérations (accès aux données, contrôle, calcul du résultat) permettent d’identifier différentes tâches qui peuvent être exécutées en parallèle.

3.1. Analyse des algorithmes 47

Dans un second temps, nous cherchons à identifier le parallélisme spatial qui est clairement mis en évidence lorsque le graphe est décomposé en sous-graphes comme nous le verrons par la suite. Cette forme de paral- lélisme correspond aux traitements réalisables en mode SIMD; elle est particulièrement visible sur le graphe d’exécution lorsque le calcul réalisé sur le pixel courant ne dépend pas du résultat du calcul sur le pixel précé- dent.

Enfin, le parallélisme au niveau instructions nécessite une étude spécifique, qui peut être globale, ou sur chacun des sous-graphes à étudier. Il peut être obtenu grâce au décompte du nombre d’opérations pouvant être exécutées simultanément. On voit par exemple, en lisant la Figure3.2de gauche à droite, qu’au maximum trois opérations peuvent être exécutées simultanément. Pour obtenir précisément ce parallélisme, les nœuds sources du graphe d’exécution sont recherchés et décomptés. Ensuite, ils sont supprimés, ce qui a pour effet de produire un nouveau graphe ayant lui même une ou plusieurs sources. Dès lors, l’opération est répétée itérativement jus- qu’à ce que le graphe ne comporte plus aucun nœud. Le parallélisme moyen au niveau instructions correspond à la moyenne de ces décomptes.