Previous Contents Next
Chapter 16 Conclusion

16.1 Prototypes
L'étude présentée dans cette thèse n'est pas que théorique. Plusieurs prototypes ont permis de vérifier nos concepts, en particulier en ce qui concerne la phase de détection des scans. Un premier prototype de détection de récurrences a été écrit en Lisp. Ce prototype comporte 20 fichiers Lisp totalisant 7000 lignes. Le logiciel est commenté à l'aide du système d'auto-documentation du projet PAF. Ce système permet d'extraire automatiquement la documentation LATEX des commentaires des fichiers sources. Les caractéristiques de ce prototype sont décrites ci-dessous. Ce prototype se montre largement capable de traiter les réductions et les scans présents dans la batterie de tests de l'Argonne (cf [15]). Les temps d'exécution et les résultats obtenus par le prototype sont donnés dans l'annexe A. Ce prototype s'est avéré capable de trouver des scans dans des situations assez difficiles comme pour le calcul d'un indice de maximum (cf A.2.5). Le cas du test A.2.15 est encore plus complexe puisque la recherche des indices est combinée avec un scan bi-directionnel. Dans un premier temps le détecteur s'est montré incapable de trouver le scan du test A.2.16 car il lui était impossible d'extraire une fonction binaire et associative de la fonction de propagation suivante :
if (a[i'] > 0) then x + a[i'] else x
Il a suffit de rajouter la règle de simplification
(if  p  then  x+e1  else  x+e2) º (x + if  p  then  e1  else  e2)  ,
pour que la détection aboutisse. Seul le test A.2.14 n'a pas pu être traité car le goto de sortie de la boucle principale, n'a pas pu être enlevé par le restructureur du compilateur PAF. L'efficacité de la détection est donc satisfaisante, par contre les temps d'exécution (en particulier pour le test A.2.15) sont élevés. Après analyse, il apparaît que plus de 75% du temps d'exécution est passé dans les opérations sur les convexes. Une autre raison de la lenteur du détecteur est le fait qu'il soit programmé en Lisp. La lenteur n'est pas tant due au fait que ce langage n'est pas vraiment compilable qu'au fait que la programmation en Lisp à tendance à accentuer la paresse du programmeur. Ainsi certains appels à des fonctions coûteuses de manipulation symbolique apparaissent, à la réflexion, inutiles. La programmation dans un langage de bas niveau comme C oblige à coder de façon efficace tout simplement parce qu'il n'existe pas de telles fonctions. Aussi nous avons démarré la construction d'un prototype de détection de scans en C. Ce prototype est en fait une (large) extension de CIPOL-light. L'interface a été étendue pour accepter en entrée un système d'équations complet et des fonctions de manipulation de systèmes ont été rajoutées.
Par exemple la session CIPOL-light ci-dessous demande le calcul du graphe des clauses d'un système d'équations particulier.
(print_dfg
 (dfg
  (system (n)
    (equation v1 (i)
       (clause (constraints (i n) (i = 1))
               (add (aref b 0) (aref a 1))
               )
       (clause (constraints (i n) (i >= 2) (i <= n))
               (subst v2
                 (add (eref v2 (function (i n) ( - i 1) n)) (aref a i))
                 0)))
    (equation v2 (i)
       (clause (constraints (i n) (i = 1) )
               (add (aref a 0) (aref b 1))
               )
       (clause (constraints (i n) (i >= 2) (i <= n))
               (add (eref v1 (function (i1 n) ( - i1 1) n)) (aref b i))
               )))))
Il est assez facile de reconnaître le système d'équations manipulé :
v1 : { i | 1 <= i <= n } cf real ;
v2 : { i | 1 <= i <= n } of real ;
v1[i] = case
         { i | i = 1 }  : b[0] + a[1] ;
         { i | i >= 2 } : v2[i-1] + a[i] ;
        esac ;
v2[i] = case
         { i | i = 1 }  : a[0] + b[1] ;
         { i | i >= 2 } : v1[i-1] + b[i] ;
        esac ;
Plus exactement, comme un mot clef subst se trouve dans la clause v1.1, le système dont le graphe des clauses est calculé est le système ci-dessus avec la référence à v2 dans v1.1 remplacée par sa valeur, c'est-à-dire :
v1 : { i | 1 <= i <= n } of real ;
v2 : { i | 1 <= i <= n } of real ;
v1[i] = case
         { i | i = 1 }  : b[0] + a[1] ;
         { i | i = 2 }  : a[0] + b[1] + a[2] ;
         { i | i >= 3 } : v1[i-2] + b[i-1] + a[i] ;
        esac ;
v2[i] = case
         { i | i = 1 }  : a[0] + b[1] ;
         { i | i >= 2 } : v1[i-1] + b[i] ;
        esac ;
Nous donnons ci-dessous un extrait de la réponse de CIPOL-light en omettant les transformations et les domaines des arcs du graphe des clauses :
( graph (
( vertex v2 0) ( vertex v2 1) ( vertex v1 0)
( vertex v1 1) ( vertex v1 2)
) (
( edge (v2 1) (v1 0) ) ( edge (v2 1) (v1 1) )
( edge (v2 1) (v1 2) ) ( edge (v1 2) (v1 0) )
( edge (v1 2) (v1 1) ) ( edge (v1 2) (v1 2) )
) )
Ce graphe contient bien les arcs attendus (la source est le second sommet d'un arc).
L'extension de CIPOL-light est construite autour d'une grammaire géré par lex et yacc, elle est constituée de 5000 lignes de code. Pour l'instant seuls les outils de base de la normalisation de systèmes sont disponibles (i.e. la substitution dans les systèmes d'équations et le choix des substitutions à appliquer). Nous avons implanté la stratégie de normalisation globale avec l'heuristique de choix des substitutions présentée en 8.4. Les résultats obtenus indiquent une accélération de l'ordre de 10 par rapport au prototype Lisp. Il faut cependant finaliser le prototype et mener une campagne de mesure sur un plus grand nombre de programmes tests pour être sûr de ce résultat. Il n'est pas prévu de réécrire tout le détecteur en C, les parties complexes comme l'extraction de la fonction binaire seront codées dans un langage de plus haut niveau.

Nous avons aussi écrit un prototype Lisp de calcul de bases de temps pour les systèmes d'équations comportant des scans explicites. Ce prototype calcule des bases de temps approximatives, basées sur des conditions de causalité approchées du type de (11.4) (voir la section 11.3). Cela est cependant suffisant pour les exemples simples (comme les tests de l'Argonne). C'est ce prototype qui a été utilisé pour les tests de la section 11.5. Notre prototype se greffe sur le prototype du professeur Feautrier de calcul des bases de temps pour des programmes sans scan explicite. La greffe ne comporte que 500 lignes de Lisp.

Aucun prototype n'a été développé pour la génération de code. En ce qui concerne les prototypes la liste des travaux futurs est :
16.2 Contribution de la thèse
Nous avons présenté une méthode de détection de scan plus précise que les méthodes existantes. Cette précision est due à l'utilisation du graphe du flot des données qui permet de traduire un programme à contrôle statique en un système d'équations équivalent. Il est alors possible de normaliser fortement le système en utilisant des substitutions de variables à la Gauss. Nous avons présenté un algorithme original pour normaliser le système. Cet algorithme se trouve être un algorithme de recherche de transversal minimal dans l'hypergraphe des circuits d'un graphe donné. Il a la bonne propriété de trouver tous les transversaux minimaux de cardinal 1 et d'être bien adapté à notre problème. Nous présentons aussi un opérateur pour exprimer les scans calculés dans les programmes impératifs à contrôle statique. Cet opérateur présente des points communs avec les opérateurs de récurrence déjà existants mais a la particularité de bien décrire les scans portant sur des tableaux multi-dimensionnels. De plus, l'opérateur Scan facilite la détection de scan multi-directionnels, c'est-à-dire de scans calculés sur plusieurs directions d'un tableau.

L'exploitation des scans détectés est aussi abordée dans cette thèse. Nous avons montré qu'il était possible de modifier les techniques classiques de l'ordonnancement pour tenir compte des scans. L'ordonnancement est calculé pour une machine virtuelle, avec un nombre illimité de processeurs et avec des UAL à nombre illimité d'entrées. Nous avons donc montré comment adapter cette base de temps pour des machines réelles. Nous avons aussi montré que deux types de codes pouvaient être générés pour un scan donné et que le choix du code le plus efficace ainsi que la génération du code pouvaient être automatisés. L'algorithme de choix du code n'est cependant qu'une heuristique en fonction du type du langage parallèle cible et du type du scan. Enfin nous avons montré que la détection de scans pouvait accélérer l'exécution des programmes traités. En effet, une accélération de 2 par rapport au code généré par le compilateur de Cray Research a été constaté sur deux exemples. Il est important de dire que ces deux exemples n'ont pas été construit pour l'occasion, il s'agit d'exemples réalistes.

Nos travaux ne sont pas limités à l'amélioration de la parallélisation de programmes numériques. En effet notre méthode de détection des scans a tendance à réécrire le programme source comme une suite de formules mathématiques. D'autres applications de ces travaux sont donc la vérification de programmes (le programme calcule-t-il la bonne formule ?) et l'ingénierie à rebours (quelle formule calcule le programme ?).
Imaginons qu'un programmeur a écrit le programme ci-dessous pour implanter l'algorithme de la remontée (voir section 12.3.3).
DO i=0,n-1
 x(n-i)=b(n-i)                           (v1)
  DO j=1,i
    x(n-i)=x(n-i)-a(n-i,n-i+j)*x(n-i+j)  (v2)
  END DO
 x(n-i)=x(n-i)/a(n-i,n-i)                (v3)
END DO
La détection de scans dans ce programme conduit à l'équation suivante :
v3 : { i | 0 <= i <= n-1 } of real ;
v3[i] = Scan( { i',j' | 0<=i'<=n-1, -1<=j<=i }, ( [0 1] ),
              +, - a[n-i',n-i'+j'] * v3[i'-j'], b[n-i] 
            )[i,i] / a[n-i.n-i] ;
Cette équation peut être traduite automatiquement en l'expression mathématique :
v3(i)=
b(n-i)-a(n-i,n-i)*b(n-i)-
i
å
j'=1
v(i-j')*a(n-i,n-i+j')
a(n-i,n-i)
Ce n'est pas l'expression attendue, le terme a(n-i,n-i)*b(n-i) ne devrait pas apparaître. Il est du au fait que la boucle sur j commence à 0 et non pas à 1.
16.3 Perspectives
D'un certain point du vue, les travaux présentés dans cette thèse visent à améliorer la parallélisation d'un programme par reconnaissance de schémas de programmation. Plus précisément certains types de calculs mal parallélisés par les compilateurs modernes sont reconnus et remplacés par des appels à des procédures de calcul optimisées. Dans cette thèse nous nous sommes limités à la détection de scans et réductions. Ces calculs sont remplacés par un appel à une procédure virtuelle ; l'opérateur Scan. Mais pourquoi se restreindre à un seul type de calcul ?

Un premier pas vers la généralisation est la détection des récurrences quelconques. Nous avons vu que notre prototype Lisp était capable de ce genre de détection, pour preuve les tests A.2.10 et A.2.11. Ces récurrences sont parallélisables car il est possible d'extraire de leurs fonctions de propagation des fonctions binaires et associatives (voir la section 10.4). Cependant, il existe une manière plus simple d'en tirer parti, il suffit de les remplacer par un appel à une procédure optimisée. Or la bibliothèque du Cray comporte de telles procédures ; FOLRP et SOLR3. Les tests A.2.10 et A.2.11 s'exécutent 50% plus rapidement sur un Cray Y-MP lorsque les récurrences sont remplacées par des appels aux procédures optimisées.

Une généralisation possible de notre méthode serait donc de détecter une partie des calculs classiques, c'est-à-dire les calculs effectués par les procédures des bibliothèques standard comme BLAS, et de les remplacer par des appels à ces procédures.
Nous n'en sommes, déjà, pas très loin. Le programme ci-dessous est une implantation courante de la multiplication de matrices.
      integer i,j,k,n
      real a(n,n),b(n,n),c(n,n),s
      do 1 i=1,n
        do 1 j=1,n
          s=0.                        (v1)
          do 2 k=1,n
   2        s = s + a(i,k) * b(k,j)   (v2)
   1      c(i,j) = s                  (v3)
Notre détecteur de scans transforme ce programme en un système d'équations très concis :
v4 : { i,j | 1<=i<=n, 1<=j<=n } of real ;
v4[i,j] = Scan( { i',j',k' | 1<=i'<=n, 1<=j'<=n, 0<=k'<=n },
                ( [0 0 1] ), +, a[i',k'] * b[k',j'], 0 )[i,j,n] ;
Il est facile de reconnaître cet opérateur Scan particulier et de générer un appel à une procédure optimisée de multiplication de matrice. Comme notre phase de normalisation est très efficace, quasiment toutes les implantations de la multiplication de matrices se ramènent à cet opérateur Scan.
Pour arriver ce résultat, il faut cependant approfondir l'étude de l'opérateur Scan. Toute une algèbre peut être développée autour de cet opérateur, certaines règles sont déjà connues comme le changement de base décrit en 12.1.1, mais il existe d'autres règles, particulièrement des règles de simplification.
Le programme de la multiplication de matrices peut être bruité, il suffit pour cela d'initialiser s avec le premier terme de la somme :
      integer i,j,k,n
      real a(n,n),b(n,n),c(n,n),s
      do 1 i=1,n
        do 1 j=1,n
          s=a(i,1)+b(1,j)             (v1)
          do 2 k=2,n
   2        s = s + a(i,k) * b(k,j)   (v2)
   1      c(i,j) = s                  (v3)
Ce programme est valide dès que les matrices ne sont pas de taille 0. Sans règle de simplification sur les opérateurs Scan, le système d'équations obtenu par le détecteur de récurrence est :
v4 : { i,j | 1<=i<=n, 1<=j<=n } of real ;
v4[i,j] = Scan( { i',j',k' | 1<=i'<=n, 1<=j'<=n, 1<=k'<=n },
              ( [0 0 1] ), +, a[i',k'] * b[k',j'],
              a[i',1] * b[1,j'] )[i,j,n] ;
Cependant, il est facile de voir que le domaine d'accumulation de cet opérateur Scan peut être étendu. Le nouvel opérateur est celui qui apparaît dans l'exemple précédent. Le prototype Lisp applique ce genre de simplification. Cela a nécessité l'écriture d'une reconnaissance de forme intégrant la plupart des règles d'algèbre linéaire. Par exemple cette reconnaissance de forme est capable d'apparier le terme i+1+x avec i en posant x=-1.
En résumé, la généralisation de notre méthode de détection de scans peut déboucher sur une nouvelle forme de parallélisation automatique par reconnaissance de schémas de programmation. Cette méthode serait bien plus efficace que les méthodes de parallélisation par simple reconnaissance de formes qu'implantent la plupart des compilateurs commerciaux car elle hériterait des points forts de notre méthode de détection de scans : Cette méthode, bien que moins élégante, pourrait même rivaliser avec la méthode de parallélisation automatique dite géométrique. En effet l'analyse ne porterait pas simplement sur les dépendances entre les opérations mais directement sur la sémantique du programme.
Previous Contents Next