PROFILING SANS EXÉCUTION

publicité
PROFILING SANS EXÉCUTION
Mémoire de fin d’études
Master d’Informatique
Etudiant : BUI Nguyen-Minh
Sous la direction de :
Professeur Danny DUBÉ
Département d’informatique, Université LAVAL
Institute de la Francophonie pour l’Informatique
Novembre 2006
Remerciements
Je voudrais remercier professeur Danny Dubé au Département d’Informatique et de Génie Logiciel à l’Université Laval pour tout ce qu’il a fait
pour moi pendant mon stage, même je n’étais pas très autonome. Je tiens
également à remercier tous les professeurs à l’IFI. Merci à mes parents et mes
amis.
1
Résumé
Dans cette mémoire, nous voudrions présenter une autre façon pour faire le
profiling. Les méthodes courantes, en général, elles ajoutent quelques morceaux de code sources et exécutent le programme pour calculer le profil d’un
programme. Par contre, notre méthode, elle n’exécute pas le programme mais
essaye de construire un système qui modèle l’exécution du programme puis
calcule le profil basé sur le résultat obtenu quand on résout le système. Cette
méthode se compose de deux phases. La première phase vise à calcul le résultat abstrait en utilisant un système de contrainte. Dans la deuxième phase,
utilisant le résultat de la première phase, on construit et résout un système
d’équation pour obtenir un résultat plus détaillé. Pourtant, la méthode de
profiling sans exécution reste de nombreux de problèmes sur le processus
de la modélisation le programme, la solution le système, la convergence du
système et l’évaluation. Nous abordons aussi quelques idées générales sur le
langage fonctionnel et le langage de programmation Scheme.
Table des matières
1 Introduction
1.1 Profiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Types de profiling . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Sujet de stage . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1
1
2
2 Langage fonctionnel
2.1 Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Particularité des langages fonctionnels . . . . . . . . . .
2.2.1 Absence des effets de bord . . . . . . . . . . . . .
2.2.2 Transparence référentielle . . . . . . . . . . . . .
2.2.3 Peu de structures de contrôle . . . . . . . . . . .
2.2.4 Fonctions comme objets de première classe . . . .
2.2.5 Gestion de la mémoire de façon automatiquement
2.3 Lambda calcul . . . . . . . . . . . . . . . . . . . . . . . .
2.3.1 Idées générales . . . . . . . . . . . . . . . . . . .
2.3.2 La syntaxe du lambda calcul . . . . . . . . . . . .
2.3.3 Alpha réduction . . . . . . . . . . . . . . . . . . .
2.3.4 Beta réduction . . . . . . . . . . . . . . . . . . .
2.4 Schème . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
3
3
3
4
5
6
6
6
6
7
8
8
8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3 Pré-traitement
9
3.1 Langage à analyser . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2 Analyse de jetons . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.3 L’algorithme en bref . . . . . . . . . . . . . . . . . . . . . . . 11
4 Analyse abstraite
4.1 Valeurs abstraites . . . . .
4.2 Idée générale . . . . . . .
4.3 Génération des contraintes
4.4 Commentaires . . . . . . .
4.5 Un exemple . . . . . . . .
.
.
.
.
.
.
.
.
.
.
i
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13
13
14
14
17
17
5 Analyse statique
5.1 Idée générale . . . . . . . . . . . . . . .
5.1.1 Définitions . . . . . . . . . . . . .
5.2 Système d’équation . . . . . . . . . . . .
5.2.1 Règles pour calculer Πl : . . . . .
5.2.2 Règles pour calculer Πx : . . . . .
5.2.3 Règles pour calculer χl et Kl (l0 ) :
5.3 Vérifier les équations . . . . . . . . . . .
5.4 Résoudre le système d’équations . . . . .
5.5 Un exemple . . . . . . . . . . . . . . . .
6 Conclusion
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
19
20
20
22
22
24
29
31
34
ii
Chapitre 1
Introduction
1.1
Profiling
Les outils pour analyser les programmes (profiling) jouent un rôle très important dans la compréhension des comportements des programmes. En effet,
les constructeurs de matériel informatique ont en besoin pour évaluer comment les programmes marcheront dans la nouvelle architecture. Ils sont aussi
indispensables pour les programmeurs dans l’analyse de leurs codes sources
et dans l’identification des morceaux importants. D’autre part, le profiling
est considéré, par les compilateurs, un outil de mesurer les algorithmes d’optimisation.
1.2
Types de profiling
En général, il y a deux types de profiling : Analyse statique de programmes
et analyse dynamique de programmes.
L’analyse statique de programmes est une famille de techniques permettant de dériver des résultats sur l’exécution de programmes sans exécuter ces
derniers.
Elle se distingue ainsi de l’analyse dynamique ou test, qui revient à essayer
le programme sur différentes entrées jugées représentatives, afin de vérifier
s’il produit les résultats attendus sur ces entrées.
L’analyse dynamique de programmes est une famille de techniques d’analyse d’exécution qui mesure le comportement d’un programme, en particulier
la fréquence et la durée des appels de fonction, quand il fonctionne. La sortie
est une suite des événements enregistrés (une trace) ou d’un résumé statistique des événements observés (un profil )
1
Actuellement, il existe un bon nombre de logiciels qui permettent de faire
l’analyse dynamique de programmes, par exemple : grof, ATOM ...
Les profileurs emploient une grande variété de techniques pour rassembler
des données, y compris des interruptions, l’instrumentation de code, des hooks
de système d’exploitation, et des compteurs d’exécution.
L’instrumentation de code, au temps de la compilation, il insère le code
dans le programme à analyser. Alors le code inséré produit des données d’analyse. Le logiciel ATOM utilise cette technique.
1.3
Sujet de stage
Notre stage a le profiling sans exécution comme le sujet principal. Cette
méthode de profiling consiste à essayer d’estimer le résultat d’un programme
et de mesurer ses comportements sans exécuter le programme.
2
Chapitre 2
Langage fonctionnel
2.1
Concepts
Il y a trois types de langage de programmation : Programmation impérative, programmation fonctionnelle et programmation logistique.
La programmation logique est une forme de programmation dont l’essence
est de définir des règles de logique mathématique au lieu de fournir une
succession d’instructions que l’ordinateur exécuterait. Le premier langage de
programmation logique est Prolog.
La programmation impérative est le style de programmation le plus utilisé, dans lequel les instructions qui modifient les données sont exécutées
simplement les unes après les autres, avec quelques structures de contrôle
permettant de créer des boucles ou des alternatives.
Le troisième type de langage de programmation est le style de programmation dans lequel on ne programme qu’en écrivant des fonctions qui appellent
d’autres fonctions. Ce paradigme de programmation fournie une abstraction
très puissante et très bien connue en mathématiques : la fonction.
2.2
2.2.1
Particularité des langages fonctionnels
Absence des effets de bord
Dans la programmation fonctionnelle, on s’affranchit de façon radicale
des effets de bord en interdisant toute opération d’assignation.
La programmation fonctionnelle souligne l’application des fonctions, contrairement à la programmation impérative qui met l’accent sur les changements
d’état et l’exécution des commandes séquentielles.
C’est pour cette raison qu’on peut éviter les effets de bord en utilisant ce
3
type de langage. En effet, pour décrire un programme, au lieu d’une machine
d’états, le paradigme fonctionnel utilise un emboîtement de fonctions considéré comme des boîtes noires qui sont imbriquées les unes dans les autres.
L’effet de bord est un effet dans lequel une fonction modifie un état autre
que sa valeur de retour. Par exemple une fonction change la valeur d’une
variable globale, donc quand on exécute cette fonction on peut obtenir deux
résultats différents.
int x = 0 ;
int getx()
{
x++ ;
return x ;
}
Cela rende souvent le comportement des programmes plus difficile à comprendre.
2.2.2
Transparence référentielle
Une autre propriété liée à la programmation fonctionnelle, c’est la transparence référentielle. Une expression est transparente de manière référentielle
si elle peut être remplacée dans le code source de programme sans changer
le résultat final du programme. En d’autres termes, cela résulte en un programme avec les même effets et sorties pour les mêmes entrées.
Un grand avantage d’écrire du code avec un style référentiellement transparent est que l’analyse de code statique est plus facile et que des transformations d’amélioration de code sont automatiquement possibles. Par exemple,
en programmation en C, il y a une pénalité de performance pour l’inclusion
d’une fonction dans une boucle, même si l’appel de fonction pourrait être déplacé à l’extérieur de la boucle sans changer les résultats du programme. Le
programmeur pourrait être forcé à faire ce déplacement, peut-être au dépend
de la lisibilité du code. Mais, si le compilateur est capable de déterminer si
l’appel de la fonction est référentiellement transparent, il peut alors effectuer
automatiquement cette transformation.
int square(int x)
{
return (x*x) ;
}
4
Donc, ce morceau de code :
int foo(int n)
{
for (int i = 0 ; i <n ; i++)
printf(square (5)) ;
}
peut être remplacé par
int foo(int n)
{
int y = square (5) ;
for (int i = 0 ; i <n ; i++)
printf(y) ;
}
2.2.3
Peu de structures de contrôle
Contrairement aux langages impératifs, dans les langages fonctionnels, les
structures de contrôle sont utilisées de façon moins fréquente que les fonctions
récursives.
Par exemple la fonction pour calculer une somme en C :
sum = 0 ;
for (i :=1 ; i<n ; i++)
sum += i ;
est récrite en Scheme :
(let loop ((i 0))
(if (= i n)
0
(+ i
(loop (+ i 1)))))
5
2.2.4
Fonctions comme objets de première classe
Les langages fonctionnels traitent les fonctions en tant qu’objets de première classe. Spécifiquement, ceci signifie que des fonctions peuvent être
créées pendant l’exécution d’un programme, être stockées en structures de
données, passer comme arguments à d’autres fonctions, et retourner comme
valeurs d’autres fonctions. Par exemple :
Création des fonctions à la volée :
(set ! f (lambda (x) (+ x 1)))
Passage en argument, retour comme résultat :
(f c (lambda (x) (g (+ x 1))))
Stockage dans les structures de données et extraction des structures de données :
(define lst (list 1 (lambda (x) ( + x 1))))
(set ! f (list-ref lst 1))
2.2.5
Gestion de la mémoire de façon automatiquement
Dans les langages fonctionnels, on utilise la gestion automatiquement de
la mémoire, donc, ces langages sont généralement plus sécuritaires mais ils
prennent plus de mémoire par rapport aux langages impératifs comme C.
2.3
2.3.1
Lambda calcul
Idées générales
Le lambda calcul a été inventé par le logicien américain Alonzo Church
dans les années 1930. Le lambda calcul joue un rôle très important dans les
langages fonctionnels parce que la fonction est le centrale dans les langages
fonctionnels.
Dans le calcul de lambda, chaque expression représente une fonction avec
un argument simple. Une fonction est anonyme définie par une expression de
lambda qui exprime l’application de la fonction sur son argument.
Par exemple, ajouter-deux la fonction f tels que f (x) = x + 2 serait
exprimé en calcul de lambda comme λx.x + 2 (ou d’une manière équivalente
comme λy.y +2, le nom de l’argument formel est peu important) et le nombre
f (3) serait écrit en tant que (λx.x + 2)3
6
L’application de fonction est associative à gauche : f xy = (f x)y.
Considérer la fonction qui prend une fonction comme argument et s’applique
avec la valeur de 3 : λf.f 3. Cette dernière fonction pourrait être appliquée à
notre plutôt ajoutent-deux la fonction comme suit : (λf.f 3)(λx.x + 2).
Donc les trois expressions (λf.f 3), (λx.x + 2) et (λx.x + 2)3 et 3 + 2 sont
équivalentes.
Une fonction de deux variables est exprimée en calcul de lambda en fonction d’un argument qui renvoie une fonction d’un argument. Par exemple, la
fonction f (x, y) = x − y serait écrite comme λy.λx.x − y ou λyx.x − y
Les trois expressions (λyx.x−y) 7 2 et (λy.7−y)2 et 7−2 sont équivalentes.
C’est cette équivalence des expressions de lambda qui en général ne peuvent
pas être décidées par un algorithme.
Les expressions de lambda ne peuvent pas toutes être réduites à une valeur
définie comme celle ci-dessus ; considérer par exemple (λx.xx)(λx.xx).
Tandis que le calcul de lambda lui-même ne contient pas des symboles
pour les nombres entiers ou l’addition, ceux-ci peuvent être définis comme
abréviations dans le calcul.
2.3.2
La syntaxe du lambda calcul
Les trois constructions principales du lambda calcul sont
– La variable, par exemple : x, y, z...
– L’application : si u et v sont deux programmes, on peut considérer
u comme une fonction et v comme un argument possible, et former
l’application uv.
– l’abstraction : si u est un programme dépendant (ou non) de la variable
x, alors on peut former un nouveau programme λx.u, qui représente la
fonction qui prend la variable x et retourne u.
D’une autre manière, on peut définir lambdaŰcalcul en utilisant la définition formelle grammaire hors-contexte BNF
< expr >::=< identif ier >
< expr >::= (λ < identif ier > . < expr >)
< expr >::= (< expr >< expr >)
En lambda calcul, l’évaluation se fait à l’aide de réductions. Les deux
réductions dont nous aurons besoin sont l’alpha - réduction qui est l’équivalent du renommage de variable et la bêta - réduction, qui est l’équivalent
de l’appel de fonction.
7
2.3.3
Alpha réduction
On peut constater que les deux expressions : λx.x+1 et λy.y +1 dénotent
la même fonction : que l’argument s’appelle x ou y, c’est toujours la fonction
qui ajoute 1 à son argument. On va donc vouloir confondre les deux termes.
L’égalité entre deux termes dont seuls les noms des variables d’entrée diffèrent
s’exprime alors par la règle suivante, dite d’alpha renommage :
λx.u =α λy.u[x → y]
2.3.4
Beta réduction
Sémantiquement, la règle importante est la beta-réduction qui exprime
que si on applique la fonction qui à x associe u à l’argument v, on obtient la
même chose que si on calcule directement u avec x remplacé par v.
(λxu)v =β u[x → v]
Par exemple, (λxx + 1)4 = 4 + 1.
2.4
Scheme
Scheme est un langage de programmation dérivé du langage fonctionnel
Lisp, créé dans les années 1970 au MIT par Gerald Jay Sussman et Guy
L. Steele. Le but des créateurs du langage était d’épurer le langage Lisp
en conservant les aspects essentiels, la flexibilité et la puissance expressive.
Scheme a donc une syntaxe extrêmement simple, avec un nombre très limité
de mots-clé.
8
Chapitre 3
Pré-traitement
3.1
Langage à analyser
Les langages courants sont trop compliqués pour faire le profiling parce
qu’ils contiennent trop de structures et de types de données (pas nécessaire
pour le but de tester le profiling). De plus, la structure de programme est
aussi compliquée. Donc, on définit un autre langage de programmation qui
est simple mais complet. Il est semblable au langage Scheme.
Syntaxe des expressions :
e ::=
|
|
|
|
|
|
|
|
|
#f constante faux
x référence
(λx.e) lambda-expression
(ee) appel de fonction
(if e e e) conditionnelle
(µx. e) point fixe
(cons e e) création de paire
(car e) extraction du CAR
(cdr e) extraction du CDR
(pair? e) test de parité
Syntaxe des valeurs :
e ::= #f constante faux
| (λx.e) fonction
9
|
(cons e e) paire
Contexte des alpha-réductions :
C α ::=
|
|
|
|
|
|
|
|
|
|
|
|
(λx.C α )
(C α e)
(e C α )
(if C α e e)
(if e C α e)
(if e e C α )
(µx. C α )
(cons C α e)
(cons e C α )
(car C α )
(cdr C α )
(pair? C α )
[.]
Règles des alpha-réductions :
C α [λx.e] →α C α [λy.(e[x → y])]
C α [µx.e] →α C α [µy.(e[x → y])]
Contexte des beta-réductions :
C β ::=
|
|
|
|
|
|
|
|
(C β e)
(v C β )
(if C β e e)
(cons C β e)
(cons v C β )
(car C β )
(cdr C β )
(pair? C β )
[.]
Règles des beta-réductions (règles d’évaluation) :
C β [(λx.e) v] →β C β [e[x → y]]
10
C β [if#f e2 e3 ]
C β [if(λx.e1 )e2 e3 ]
C β [if(consv1 v2 )e2 e3 ]
C β [µx.e]
C β [car (cons v1 v2 )]
C β [cdr (cons v1 v2 )]
C β [pair? #f ]
C β [pair? (λx.e)]
C β [pair? (cons v1 v2 )]
3.2
→β
→β
→β
→β
→β
→β
→β
→β
→β
C β [e3 ]
C β [e2 ]
C β [e2 ]
C β [e[x → (µx.e)]]
C β [v1 ]
C β [v2 ]
C β [#f ]
C β [#f ]
C β [#f ]
Analyse de jetons
Pour les langages impératifs comme C, notre entrée - le programme à analyser est considéré comme une suite de caractère, donc tout d’abord il faut
faire l’analyse de jetons pour découper et regrouper les caractères constituant un programme en jetons. Mais, en utilisant Scheme avec le langage à
analyser au dessus, on peut omettre cette phase. Scheme analyse automatiquement l’entrée et retourne le code source de programme à analyser comme
une expression des symboles.Par exemple,l’expression : (cons #f #f ) est
automatiquement ‘interprétée’ comme une liste de trois symboles, (mais pas
une paire).
3.3
L’algorithme en bref
Cette méthode est divisée en deux phases. La première phase vise à calculer le résultat abstrait en utilisant un système de contrainte. Basant sur le
résultat de la première phase, on construit et résout, dans la phase suivante,
un système d’équation pour obtenir un résultat plus exact.
Par exemple, avec une expression simple :
E = (if (#f ) (#f ) (cons (#f ) (#f )))
Après la première phase, on obtient les valeurs possibles de E sont #f ou
une paire (#f, #f ).
Après la deuxième phase on obtient les valeurs possibles de E sont : #f
avec la probabilité de 0% et une paire (#f, #f ) avec la probabilité de 100%.
Normalement, le résultat de notre méthode est une liste de valeurs, chaque
11
valeur est liée à une probabilité, mais, en réalité, chaque expression retourne
seulement une valeur en exécutant.
12
Chapitre 4
Analyse abstraite
Dans ce chapitre, on va présenter une analyse abstraite sur le code source
d’un programme. Le résultat de cette phase est nécessaire pour faire le profiling dans la deuxième phase.Cette analyse a pour but d’estimer toutes les
valeurs possibles que chaque expression dans le programme puisse créer sans
compter la probabilité de chaque valeur.
Pour faciliter l’analyse abstraite, toutes les expressions dans le programme
sont étiquetées (avec des étiquettes différentes). Par exemple :
(cons1 (#f2 ) (#f3 ))
4.1
Valeurs abstraites
On définit les valeurs abstraites suivantes :
– #f : valeur de faux
– Pi : une paire à la position d’un (sous)-expression ayant l’étiquette i)
– λi : une fonction à la position d’un (sous)-expression ayant l’étiquette i)
– ERROR : une erreur
– ⊥ : valeur infinie, quand l’expression ne retourne pas
Avec les définitions au dessous, on peut dire que la valeur de E = (cons1 (#f2 ) (#f3 ))
est P1 et la valeur de l’appel de fonction E = (call1 (#f2 ) (#f3 )) est
ERROR.
13
On dénote αi l’ensemble des valeurs que l’expression ayant l’étiquette i
peut retourner.
4.2
Idée générale
L’idée générale de cet algorithme est qu’on essaie de calculer la valeur retournée d’un expression en combinant les valeurs des sous-expressions (après
vérifier les relations entres les sous-expressions).
Par exemple, pour l’expression
E = (if1 e2 e3 e4 )
S
on a α1 = α3 α4 .
Mais on constate que cette estimation est un peu faible parce qu’on ne compte
pas les valeurs de e2 . Une autre estimation plus exacte est comme suivante :
α2 ⊆ {#f } ⇒ α1 ⊆ α4
(α2 \{#f } =
6 ) ⇒ α1 ⊆ α3
(notons que dans Scheme, et dans notre langage, tout est vrai sauf la valeur
de faux #f )
Il nous reste un autre problème à résoudre : comment peut-t-on éliminer
les codes mortes (c’est à dire les morceaux de code qui ne sont jamais exécutés) pour qu’on ne doive pas calculer ses valeurs, et ses valeurs n’affectent
pas d’autres estimations ?
On dénote δi la valeur booléenne vraie/faux, qui indique si l’expression
ayant l’étiquette i est exécutée.
Au début, seulement δ1 est vraie, tous les autres δ sont associés à une fausse
valeur. Pas à pas, en vérifiant la structure de programme, on ‘allume’ les
autres δ jusqu’à il n’y pas de nouveau changement d’état de δ.
Par exemple :
si el = (consl e1 e2 ) alors δl ⇒ δ1 et δl ⇒ δ1
4.3
Génération des contraintes
Ensuite, on présente le système des contraintes qui sont construits à partir
du code source de programme.
14
– Si el = #fl alors
δl ⇒ αl ⊆ {#f }
– Si el = xl alors
δl ⇒ αl ⊆ αx
– Si el = (λl x. el1 ) alors :
δl ⇒ αl ⊆ {λl }
(αx 6= ) ⇒ δl1
– Si el = (l el1 el2 ) alors :
δl ⇒ δl1
δl ⇒ δl2
pour tout λl3 ∈ αl1 tel que el3 = (λl3 x. el4 ) alors
αx ⊆ αl2
αl ⊆ αl4
αl1 ⊆ {Pi } ⇒ αl ⊆ ERROR
αl1 ⊆ {#f } ⇒ αl ⊆ ERROR
αl1 ⊆ {ERROR} ⇒ αl ⊆ ERROR
αl2 ⊆ {ERROR} ⇒ αl ⊆ ERROR
– Si el = (ifl el1 el2 el3 ) alors :
δl ⇒ δl1
(αl1 \{#} =
6 ) ⇒ δl2
(#f ∈ αl1 ) ⇒ δl3
αl ⊆ αl2 ∪ αl3
(ERROR ∈ αl1 ) ⇒ αl ⊆ ERROR
– Si el = (µl x. el1 ) alors :
δ1
αl
αx
(ERROR ∈ αl1 )
15
⇒
⊆
⊆
⇒
δl1
αl1
αl1
αl ⊆ ERROR
– Si el = (consl el1 el2 ) alors :
δ1
δ1
δ1
αl1 ⊆ {ERROR}
αl2 ⊆ {ERROR}
⇒
⇒
⇒
⇒
⇒
δl1
δl2
αl ⊇ {Pl }
αl ⊆ ERROR
αl ⊆ ERROR
– Si el = (carl el1 ) alors :
δ1 ⇒ δl1
pour tout Pl2 ∈ αl1 tel que el2 = (consl2 el3 el4 ) alors
αl ⊆ αl3
αl1 ⊆ {λi } ⇒ αl ⊆ ERROR
αl1 ⊆ {#f } ⇒ αl ⊆ ERROR
αl1 ⊆ {ERROR} ⇒ αl ⊆ ERROR
– Si el = (cdrl el1 ) alors :
δ1 ⇒ δl1
pour tout Pl2 ∈ αl1 tel que el2 = (consl2 el3 el4 ) alors
αl ⊆ αl4
αl1 ⊆ {λi } ⇒ αl ⊆ ERROR
αl1 ⊆ {#f } ⇒ αl ⊆ ERROR
αl1 ⊆ {ERROR} ⇒ αl ⊆ ERROR
– Si el = (pair?l el1 ) alors :
δ1 ⇒ δl1
soit π = {Pl2 ∈ αl1 }
αl ⊆ π
si αl1 \π 6= alors αl ⊆ {#f }
αl1 ⊆ {ERROR} ⇒ αl ⊆ ERROR
Contrainte additionnelle sur le programme :
E = e1 et δ1 = vrai
16
4.4
Commentaires
L’analyse reste conservatrice mais elle est relativement précise. Tous les
trois types d’objets sont pris en compte mais seulement les donnes pertinentes
circulent et seulement les expressions qui doivent être évaluées sont calculées.
4.5
Un exemple
Prenons un exemple de cette analyse. Supposons qu’on doit analyser le
programme suivant :
(1 (λ2 f
(if3 #f4
(5 f6 #f7 )
(8 f9 (cons10 #f11 #f12 )))
(λ13 x (car14 x15 ))))
Tout d’abord, on met δ1 = #t, et tous les autres δ = #f . On a un système
de contrainte :
δ1 ⇒ δ2
δ1 ⇒ δ13
pour tout λl3 ∈ α2 tel que el3 = (λl3 x. el4 ) alors
αf ⊆ α13
αl ⊆ αl4
δ2 ⇒ α2 ⊆ {λ2 }
(αf 6= ) ⇒ δ3
...
À partir de δ1 et les valeurs natives des expressions, on fait l’itération et la
substitution jusqu’au moment où il n’y pas de changement de δ. Le résultat
final est donc comme suivant :
α1
α2
αf
α3
α4
α5
=
=
=
=
=
=
#f , δ1 = #t
λ2 , δ2 = #t
{λ13 }
{#f }, δ3 = #t
{#f }, δ4 = #t
, δ5 = #f
17
α6
α7
α8
α9
α10
α11
α12
α13
αx
α14
α15
=
=
=
=
=
=
=
=
=
=
=
, δ6 = #f
, δ7 = #f
{#f }, δ8 = #t
{λ13 }, δ9 = #t
{P10 }, δ10 = #t
{#f }, δ11 = #t
{#f }, δ12 = #t
{λ13 }, δ13 = #t
{P10 }
{#f }, δ14 = #t
{P10 }, δ15 = #t
18
Chapitre 5
Analyse statique
L’algorithme mentionné dans le chapitre précédent ne donne qu’une analyse ‘abstraite’. Dans ce chapitre, nous voudrions présenter donc une autre
méthode permettant de nous apporter un résultat plus détaillé. Dans cette
méthode, on estime non seulement le résultat de chaque expression mais aussi
la fréquence que chacune (sous-)expression est exécutée dans l’exécution du
programme.
5.1
Idée générale
5.1.1
Définitions
Pour faciliter l’illustration de notre méthode, nous donnons quelques définitions suivantes :
– Πl (v) : On dénote Πl (v) la probabilité de el lorsqu’évalué vaut v, v
est un élément de l’ensemble de valeurs possibles - les valeurs qu’on a
obtenu après la phase 1.
– χl : On dénote χl la probabilité que el soit la prochaine expression à
évaluer.
Donc, le but de notre projet est de calculer les Πl (v) et χl .
– On définit les ensembles suivants :
Val = {#f, ERROR, ⊥} ∪ {λi |i ∈ Label} ∪ {Pi |i ∈ Label} - l’ensemble de tous les valeurs possibles
Val+ = {#f } ∪ {λi |i ∈ Label} ∪ {Pi |i ∈ Label} ∪ {µi |i ∈ Label}
19
OK = {#f } ∪ {λi |i ∈ Label} ∪ {Pi |i ∈ Label}
TRUE = {λi |i ∈ Label} ∪ {Pi |i ∈ Label}
On définit :
– Kl (l0 ) est la probabilité que el0 soit la prochaine expression à évaluer
une fois que el est fini.
– Πx (v) est la probabilité que la variable x vaut v pendant l’exécution
du programme.
– Πl (S) =
P
– Πx (S) =
P
v∈S
v∈S
Πl (v) si S ⊆ Val
Πx (v) si S ⊆ Val+
L’idée principale ici, c’est qu’on essaye de modeler le programme comme
un système de contrainte (sous une forme d’un système d’équation entre les
Πl (v),χl et Kl (v)). Puis, on cherche une solution ‘acceptable’ de ce système.
On espère que cette solution ‘se reflète’ les valeurs de Π et χ.
5.2
Système d’équation
Les règles pour calculer les Πl (v),χl et Kl (v)) ici sont basées sur l’idée :
"Si on connaît les valeurs des Πl (v),χl et Kl (v) au pas n , comment peut-on
les calculer au pas n + 1 ?"
5.2.1
Règles pour calculer Πl :
– Pour el = #fl
Πl (v) =

 1 si v = #f ;

si v ∈ Val
0 si non.
– Pour el = xl
Πl (v) = Πx (v) +
X
µi ∈V al+
20
Πx (µi )Πi (v) si v ∈ Val
– Pour el = (λl x.el0 )
Πl (v) =

 1 si v = λl ;

si v ∈ Val
0 si non.
– Pour el = (l el0 el00 )
X
Πl (v) = Πl00 (OK)
Πl0 (λl000 )Πl0000 (v) si v ∈ OK
λl000 ∈V al, el000 =(λl000 x. el0000 )
Πl (ERROR) = Πl0 (ERROR) + Πl0 (OK)Πl00 (ERROR) +
!
X
Πl00 (OK) Πl0 (#f ) +
Πl0 (Pi ) +
Pi ∈V al
X
00
Πl (OK)
Πl0 (λl000 )Πl0000 (ERROR)
λl000 ∈V al, el000 =(λl000 x. el0000 )
X
Πl (⊥) = Πl0 (⊥) + Πl0 (OK)Πl00 (⊥) + Πl00 (OK)
Πl0 (λl000 )Πl0000 (⊥)
λl000 ∈V al, el000 =(λl000 x. el0000 )
– Pour el = (ifl el0 el00 el000 )
Πl (v) = Πl0 (T RU E)Πl00 (v) + Πl0 (#f )Πl000 (v) si v ∈ OK
Πl (ERROR) = Πl0 (ERROR) + Πl0 (T RU E)Πl00 (ERROR) + Πl0 (#f )Πl000 (ERROR)
Πl (⊥) = Πl0 (⊥) + Πl0 (T RU E)Πl00 (⊥) + Πl0 (#f )Πl000 (⊥)
– Pour el = (µl x.el0 )
Πl (v) = Πl0 (v) si v ∈ V al
– Pour el = (consl el0 el00 )
Πl (v) = 0 si v ∈ OK \ {Pl }
Πl (Pl ) = Πl0 (OK)Πl00 (OK)
Πl (v) = Πl0 (v) + Πl0 (OK)Πl00 (v) si v ∈ {ERROR, ⊥}
21
– Pour el = (carl el0 )
P
Πl (v) = Pl00 ∈V al,
Π
el00 =(consl00 el000 el0000 )
(v)Π
(OK)
Πl0 (Pl00 ) Π 000l000(OK)Πl00000000 (OK) si v ∈ OK
l
Πl (ERROR) = Πl0 (ERROR) + Πl0 (#f ) +
l
P
λi ∈V al
Πl0 (λi )
Πl (⊥) = Πl0 (⊥)
– Pour el = (cdrl el0 )
P
Πl (v) = Pl00 ∈V al, el00 =(consl00
Π
el000 el0000 )
(v)Π
(OK)
l000
Πl0 (Pl00 ) Π 000l0000
si v ∈ OK
(OK)Π 0000 (OK)
l
Πl (ERROR) = Πl0 (ERROR) + Πl0 (#f ) +
l
P
λi ∈V al
Πl0 (λi )
Πl (⊥) = Πl0 (⊥)
– Pour el = (pair ?l el0 )
Πl (#f ) = Πl0 (#f ) +
P
λi ∈V al
Πl0 (λi )
Πl (λi ) = 0 pour tout i ∈ Label
Πl (v) = Πl0 (v) si v ∈ {ERROR, ⊥} ∪ {Pi |i ∈ Label}
5.2.2
Règles pour calculer Πx :
– Pour x dans (λl x.el0 )
P
Πx (v) = P
l00 ∈Label, el00 =(l00 el000 el0000 )
l00 ∈Label,
el00 =(l00 el000
χl00 Πl000 (λl )Πl0000 (v)
el0000 ) χl00 Πl000 (λl )Πl0000 (OK)
si v ∈ V al+
– Pour x dans (µl x.el0 )
Πx (v) =
5.2.3
1 si v = µl ;
0 si non.
si v ∈ V al+
Règles pour calculer χl et Kl (l0 ) :
Ici, les règles sont présentés de façon un peu différente : On traverse
toutes les expressions dans le programme, à chaque expression, on ‘distribue’
quelques parties de ’vieux’ χl et Kl (l0 ) aux nouveaux. C’est pourquoi on utilise l’opérateur + =
22
– Pour el = #fl :
∀l0 ∈ Label : χl0 + = χl Kl (l0 )
– Pour el = xl :
∀l0 ∈ Label : χl0 + = χl Πx (OK)Kl (l0 )
∀l0 ∈ Label : soit el0 = µl0 x. el00 :
χ00l + = χl Πx (µl0 )
Kl00 + = χl Πx (µl0 )Kl
– Pour el = (λl x.el0 ) :
∀l00 ∈ Label : χl00 + = χl Kl (l00 )
– Pour el = (l el0 el00 ) :
χl0 + = χl , Kl0 = δl00
δ est la fonction de Dirac, tous les valeurs valent 0 sauf la valeur à l00 vaut 1
∀l000 ∈ Label, soit el000 = (λl000 x.(l0000 )) :
Kl00 (l0000 )+ =
Πl0 (λl000 )
Πl0 (OK)
Kl0000 + = χl Π0l (λ000
l )Πl00 (OK)
Kl00 (1)+ =
Πl0 ({#f } ∪ {Pi })
Πl0 (OK)
– Pour el = (ifl el0 el00 el000 )
χl 0 + = χl
Kl0 (el00 )+ =
Πl0 (T RU E)
Πl0 (OK)
Kl0 (el000 )+ =
Πl0 (#f )
Πl0 (OK)
Kl00 + = Kl
Kl000 + = Kl
– Pour el = (µl x.el0 )
χl 0 + = χl
K l 0 + = χ l Kl
23
– Pour el = (consl el0 el00 )
χl 0 + = χl
Kl0 + = δl00
Kl00 + = Kl
– Pour el = (carl el0 )
χl 0 + = χl
Πl0 (Pi )
Kl
Πl0 (OK)
Πl0 ({#f } ∪ {λi })
Kl0 (1)+ =
Πl (OK)
Kl 0 + =
– Pour el = (cdrl el0 )
χl 0 + = χl
Πl0 (Pi )
Kl
Kl 0 + =
Πl0 (OK)
Πl0 ({#f } ∪ {λi })
Kl0 (1)+ =
Πl (OK)
– Pour el = (pair ?l el0 )
χl 0 + = χl
K l 0 + = Kl
– Pour le programme principale.
K1 = δ1
5.3
Vérifier les équations
Nous voudrions que ce système bien simule notre programme, dons c’est
important que les Πl , Πx , χl respectent toujours quelques critères. On va
prouver que, au pas n, si on construit les valeurs de Πl , Πx , χl basées sur les
valeurs de Πl , Πx , χl au pas n − 1, on a toujours
P
v∈V al Πl (v) = 1 pour chaque l
P
v∈V al+
P
Πx (v) = 1 pour chaque x
l∈Label
χl = 1
24
En fait, ces critères sont satisfaits naturellement parce qu’en écrivant les
équations, on a tenu compte de ces critères. Par exemple avec l’expression
el = (ifl el0 el00 el000 ). On utilise les équations :
Πl (v) = Πl0 (T RU E)Πl00 (v) + Πl0 (#f )Πl000 (v) si v ∈ OK
Πl (ERROR) = Πl0 (ERROR) + Πl0 (T RU E)Πl00 (ERROR) + Πl0 (#f )Πl000 (ERROR)
Πl (⊥) = Πl0 (⊥) + Πl0 (T RU E)Πl00 (⊥) + Πl0 (#f )Πl000 (⊥)
On peut constater facilement
qu’on a ‘traversé’ toutes les valeurs possibles
P
de el , el0 et el00 donc v∈V al Πl (v) = 1. On donne ici une démonstration plus
claire.
– Pour Πl (v)
(n)
On dénote Πl (v) est la valeur de Πl (v) au pas n
– Si el = #fl
(n)
(n)
(n)
Πl (V al) = Πl (#f ) + Πl ({V al \ {#f }})
= 1+0
= 1.
– Si el = (λl x.el0 )
(n)
(n)
(n)
Πl (V al) = Πl (λl ) + Πl ({V al \ {λl }})
= 1+0
= 1.
– Si el = xl
A partir de l’équation
X
Πl (v) = Πx (v) +
µi ∈V
Πx (µi )Πi (v) si v ∈ V al
al+
On a
(n)
Πl (V al) = Π(n−1)
(V al+ ) − Π(n−1)
({µi |i ∈ Label}) +
x
x
X
(n−1)
Π(n−1)
(µi )Πi
x
µi ∈V al+
({µi |i ∈ Label}) +
= Π(n−1)
(V al+ ) − Π(n−1)
x
x
X
µi ∈V al+
(n−1)
parce que Πi
(V al+ )
= Π(n−1)
x
= 1.
25
(V al) = 1
Π(n−1)
(µi )
x
(V al)
– Si el = (l el0 el00 )
(n)
(n)
(n)
(n)
Πl (V al) = Πl (OK) + Πl (ERROR) + Πl (⊥)
X
(n−1)
(n−1)
(n−1)
= Πl00 (OK)
Πl 0
(λl000 )Πl0000 (OK) +
λl000 ∈V al, el000 =(λl000 x. el0000 )
(n−1)
Πl 0
(n−1)
(n−1)
(ERROR) + Πl0
(OK)Πl00 (ERROR) +
!
X
(n−1)
(n−1)
(n−1)
Πl00 (OK) Πl0
(#f ) +
Πl 0
(Pi ) +
Pi ∈V al
X
(n−1)
Πl00 (OK)
(n−1)
Πl 0
(n−1)
(λl000 )Πl0000 (ERROR) +
λl000 ∈V al, el000 =(λl000 x. el0000 )
(n−1)
Πl 0
(⊥) +
(n−1)
Πl00 (OK)
(n−1)
Πl0
(n−1)
(OK)Πl00
X
(⊥) +
(n−1)
(λl000 )Πl0000 (⊥)
(n−1)
(λl000 )
Πl 0
(n−1)
λl000 ∈V al, el000 =(λl000 x. el0000 )
=
X
(n−1)
Πl00 (OK)
Πl 0
λl000 ∈V al, el000 =(λl000 x. el0000 )
(n−1)
(n−1)
(n−1)
Πl0000 (OK) + Πl0000 (ERROR) + Πl0000 (⊥) +
!
X (n−1)
(n−1)
(n−1)
Πl00 (OK) Πl0
(#f ) +
Πl 0
(Pi ) +
Pi ∈V al
(n−1)
(OK) Πl00
(n−1)
(ERROR) + Πl0
Πl 0
Πl 0
(n−1)
(n−1)
(ERROR) + Πl00
(n−1)
(⊥) +
(⊥)
Parce que
(n−1)
(n−1)
(n−1)
Πl0000 (OK) + Πl0000 (ERROR) + Πl0000 (⊥) = 1
On a
(n)
(n−1)
Πl (V al) = Πl00

(OK)

X
(n−1)
Πl 0

(n−1)
(λl000 ) + Πl0
(#f ) +
Pi ∈V al
λl000 ∈V al, el000 =(λl000 x. el0000 )
(n−1)
(n−1)
(n−1)
Πl 0
(OK) Πl00 (ERROR) + Πl00 (⊥) +
(n−1)
(ERROR) + Πl0
(n−1)
(OK)Πl0
Πl 0
= Πl00
(n−1)
(n−1)
26
X
(⊥)
(OK) +
(n−1)
Πl 0
(Pi ) +
(n−1)
(n−1)
(OK) 1 − Πl00 (OK) +
(n−1)
(ERROR) + Πl0
Πl 0
Πl 0
(n−1)
(n−1)
Πl0
(OK)
=
= 1
+
(⊥)
(n−1)
Πl0
(ERROR)
+ Πl0
(n−1)
(n)
(n)
(⊥)
– Si el = (ifl el0 el00 el000 )
(n)
(n)
Πl (V al) = Πl (OK) + Πl (ERROR) + Πl (⊥)
(n−1)
(T RU E)Πl00
(n−1)
(ERROR) + Πl0
= Πl0
Πl 0
=
(n−1)
(n−1)
(OK) + Πl0
(n−1)
(n−1)
(#f )Πl000
(n−1)
(T RU E)Πl00
(OK) +
(ERROR) +
(n−1)
(n−1)
Πl 0
(#f )Πl000 (ERROR) +
(n−1)
(n−1)
(n−1)
(n−1)
(n−1)
Πl 0
(⊥) + Πl0
(T RU E)Πl00 (⊥) + Πl0
(#f )Πl000 (⊥)
(n−1)
(n−1)
(n−1)
(n−1)
Πl0
(T RU E) Πl00 (OK) + Πl00 (ERROR) + Πl00 (⊥)
(n−1)
(n−1)
(n−1)
(n−1)
Πl 0
(#f ) Πl000 (OK) + Πl000 (ERROR) + Πl000 (⊥) +
(n−1)
(ERROR) + Πl0
(n−1)
(T RU E) + Πl0
Πl 0
= Πl0
= 1.
(n−1)
(n−1)
(⊥)
(n−1)
(#f ) + Πl0
(n−1)
(ERROR) + Πl0
– Si el = (µl x.el0 ). On a
(n)
(n−1)
Πl (V al) = Πl0
(V al) = 1
– Si el = (consl el0 el00 )
(n)
(n)
(n)
(n)
Πl (V al) = Πl (OK \ {Pl }) + Πl (Pl ) + Πl ({ERROR, ⊥})
(n−1)
= 0 + Πl0
=
(n−1)
(OK)Πl00
(n−1)
(OK) + Πl0
({ERROR, ⊥}) +
(n−1)
(n−1)
Πl 0
(OK)Πl00 ({ERROR, ⊥})
(n−1)
(n−1)
(n−1)
Πl0
(OK)Πl00 (OK) + Πl0
({ERROR, ⊥})
(n−1)
(n−1)
Πl 0
(OK) 1 − Πl00 (OK)
(n−1)
= Πl0
= 1.
(n−1)
({ERROR, ⊥}) + Πl0
(OK)
– Si el = (carl el0 )
(n)
(n)
(n)
(n)
Πl (V al) = Πl (OK) + Πl (ERROR) + Πl (⊥)
27
+
(⊥)
+
X
=
Pl00 ∈V al, el00 =(consl00 el000
(n−1)
Πl 0
(n−1)
(n−1)
Πl000 (OK)Πl0000 (OK)
(n−1)
Πl 0
(Pl00 )
(n−1)
Πl00 (Pl00 )
el0000 )
(n−1)
(ERROR) + Πl0
X
(#f ) +
(n−1)
Πl 0
(n−1)
(λi ) + Πl0
+
(⊥)
λi ∈V al
X
=
(n−1)
Πl 0
(Pi )
+
(n−1)
Πl0
({ERROR, #f, ⊥})
+
Pi ∈V al
X
(n−1)
Πl 0
(λi )
λi ∈V al
= 1.
– Si el = (cdrl el0 )
(n)
(n)
(n)
(n)
Πl (V al) = Πl (OK) + Πl (ERROR) + Πl (⊥)
(n−1)
(n−1)
X
Π 0000 (OK)Πl000 (OK)
(n−1)
+
Πl 0
(Pl00 ) l
=
(n−1)
00
(P
)
Π
00
l
l
Pl00 ∈V al, el00 =(consl00 el000 el0000 )
X
(n−1)
(n−1)
(n−1)
(n−1)
Πl 0
(λi ) + Πl0
(⊥)
Πl 0
(ERROR) + Πl0
(#f ) +
λi ∈V al
=
X
(n−1)
Πl 0
(Pi )
+
(n−1)
Πl0
({ERROR, #f, ⊥})
+
Pi ∈V al
X
(n−1)
Πl 0
(λi )
λi ∈V al
= 1.
– Si el = (pair?l el0 )
(n)
X
(n)
Πl (V al) = Πl (#f ) +
(n)
Πl (λi ) +
λi ∈V al
=
(n−1)
(Πl0
(#f )
X
+
X
(n−1)
(n)
Pi ∈V al
(n−1)
Πl 0
(λi ))
+0+
X
(n−1)
Πl 0
(Pi ) +
Pi ∈V al
λl ∈V al
Πl 0
= 1.
(n)
Πl (Pi ) + Πl ({ERROR, ⊥})
({ERROR, ⊥})
– Pour Πx (v)
– Pour x dans (λl x.el0 )
P
Πx (V al+ ) =
l00 ∈Label, el00 =(l00 el000 el0000 )
χl00 Πl000 (λl )Πl0000 (OK ∪ {µi |i ∈ Label})
P
l00 ∈Label, el00 =(l00 el000 el0000 )
χl00 Πl000 (λl )Πl0000 (OK)
parce que Πl0000 (µi ) = 0 pour tout i ∈ Label
– Pour x dans (µl x.el0 )
Πx (V al+ ) = Πx (µl ) + Π(V al+ \ {µl }) = 1 + 0 = 1.
28
= 1.
– Pour χl : Dans toutes les équations, la valeur de χl est toujours transférée à l’autre expression. Donc la somme de χ est conservée.
5.4
Résoudre le système d’équations
On peut écrire notre système sous la forme suivante
X = F (X)
(5.1)
où X(x1 , x2 , . . . , xn ) est un vecteur de n éléments, x1 , x2 , . . . , xn sont les valeurs de Πl (v), Πv (v) et χl , de plus on a 0 ≤ xi ≤ 1 pour tout 1 ≤ i ≤ n.
– La méthode, la plus simple pour résoudre (1) est Gauss-Seidel interation [5]. Le principe de cette méthode est d’appliquer directement la
formule Xn+1 = F (Xn ) avec une valeur d’initialisation X0 . Le problème
le plus difficile est de déterminer la convergence de cette méthode. Il y
a deux critères courants :
Application contractante Le critère d’application contractante ou
contracting map [6]. Le contenu de ce théorème est comme suivant :
Soient E un espace métrique complet (non vide) et f une application contractante de E dans E (avec le coefficient k < 1). Alors
il existe un point fixe unique x∗ de f dans E (c’est-à-dire tel que
f (x∗ ) = x∗ ). De plus toute suite d’éléments de E vérifiant la récurrence xn+1 = f (xn ) converge vers x∗
D’une autre manière, le méthode Gauss-Seidel converge si pour
tous x, y ∈ D, D est un sous-ensemble de Rn , il existe α ∈ [0, 1)
tels que k F (x) − F (y) k≤ α k x − y k où k . k est une certaine
norme (norme Euclide par exemple)
Il est difficile d’appliquer ce critère à notre système parce que
notre système est trop grand et nous ne connaissons pas d’avance
les coefficients. En générale, d’après [6], ce critère n’est pas adéquat pour les systèmes d’équation non-linéaire.
Premiers dérivés partiels Un autre critère qui utilise les dérivés
partiels est présenté dans [7].
Une condition suffisante pour que l’itération converge est
n X
∂fj ∗ ∗
∗
< 1 pour tout 1 ≤ j ≤ n
(x
,
x
,
.
.
.
,
x
)
1
2
n
∂xi
i=1
29
avec le point d’initialisation X0 assez proche du point fixe X ∗ (x∗1 , x∗2 , . . . , x∗n ).
Malheureusement, on ne peut pas utiliser ce critère avec notre
système, on peut citer des fi où le critère n’est pas satisfait, par
exemple avec el = (carl el0 )
X
f2 = Πl (ERROR) = Πl0 (ERROR) + Πl0 (#f ) +
Πl0 (λi )
λi ∈V al
on a
n X
∂f2 ∗ ∗
∗
∂xi (x1 , x2 , . . . , xn ) > 1
i=1
– La méthode la plus courante (et peut-être, la seule méthode générale
pour résoudre les systèmes d’équation non-linéaire) est Newton [6] avec
plusieurs variantes. En fait, la méthode de Gauss-Seidel est un cas spécial de Newton où on ignore toutes les portions dérivés dans le Taylor
série.
Soit F : Rn −→ Rn , F est continûment dérivable, et J est le déterminant Jacobien de F :
J = JF (x1 , . . . , xn ) =
∂(f1 , . . . , fn )
∂(x1 , . . . , xn )
En utilisant la formule de Taylor, on a
F (x + ∂x) = F (x) + J.∂x + O(∂x2 )
Supposons que l’on doit résoudre le système F (X) = 0, en omettant
O(∂x2 ) on peut appliquer l’itération suivante, à chaque itération, on
résout le système d’équation non-linéaire pour trouver ∂ k :
J(X k )∂ k = −F (X k )
(5.2)
et on calcule la valeur de X (k+1) :
X (k+1) = X k + ∂ k
Pour un système avec plusieurs inconnues, cela prend beaucoup de
temps à calculer (2), donc on a proposé quelques variantes [8] [9] [5] ,
en générale on ne calcule pas directement J(X k ).
Il nous reste un autre problème à vérifier, c’est la convergence de cette
méthode. Il y a deux types de convergence.
Convergence globale Il s’agit de la convergence de notre système
avec un certain point d’initialisation. Il est difficile de l’appliquer
à notre système.
30
Convergence locale Cette méthode converge avec un point d’initialisation "suffisamment proche" de la racine. Pour vérifier ce critère,
il faut faire quelques calculs sur J similaires au critère d’application contractante, alors il est aussi difficile de le réaliser.
En résumé,il semble que les méthodes de (quasi-)Newton sont trop lourdes
à appliquer car notre système a beaucoup d’inconnue même si, en générale,
elles convergent. La méthode de Gauss-Seidel est plus facile à réaliser mais
on ne peut pas assurer sa convergence. Avec notre système, on applique la
méthode de Gauss-Seidell, on fait l’itération certaines fois et puis vérifie la
convergence en examinant la valeur de ∂ n .
On utilise une autre technique appliquée dans le réseau de neutron - le
taux d’apprentissage : le résultat final au pas n est la combinaison entre le
résultat au pas n − 1 et le résultat ‘brut’ au pas n :
(n)
χl
(n−1)
= χl
(n)
∗ (1 − α) + χl,brut ∗ α
α est le taux d’apprentissage, 0 < α < 1.
5.5
Un exemple
On présente ici un exemple pour illustrer notre méthode. Supposons qu’on
voudrait faire le profiling de ce programme : (voir l’exemple dans le chapitre
précédent)
(1 (λ2 f
(if3 #f4
(5 f6 #f7 )
(8 f9 (cons10 #f11 #f12 )))
(λ13 x (car14 x15 ))))
Après la phase d’analyse abstraite, on a le résultat suivant :
α1
α2
αf
α3
α4
α5
α6
=
=
=
=
=
=
=
#f , δ1 = #t
λ2 , δ2 = #t
{λ13 }
{#f }, δ3 = #t
{#f }, δ4 = #t
, δ5 = #f
, δ6 = #f
31
α7
α8
α9
α10
α11
α12
α13
αx
α14
α15
=
=
=
=
=
=
=
=
=
=
, δ7 = #f
{#f }, δ8 = #t
{λ13 }, δ9 = #t
{P10 }, δ10 = #t
{#f }, δ11 = #t
{#f }, δ12 = #t
{λ13 }, δ13 = #t
{P10 }
{#f }, δ14 = #t
{P10 }, δ15 = #t
Le système d’équation qu’on obtient de ce programme est :
Π1 (#f )
Π1 (⊥)
Π2 (λ2 )
Π3 (#f )
Π3 (⊥)
Π4 (#f )
...
=
=
=
=
=
=
Π13 (OK) ∗ Π2 (λ2 )Π3 (#f )
Π2 (⊥) + Π2 (OK)Π13 (⊥) + Π13 (OK)Π2 (λ2 )Π3 (⊥)
1
Π4 (T RU E)Π5 (#f ) + Π4 (#f )Π8 (#f )
Π4 (⊥) + Π4 (T RU E)Π5 (⊥) + Π4 (#f )Π8 (⊥)
1
Au pas 0, on met toutes les valeurs de Πl (v) égales à
1
kαl k
- c’est à dire toutes les valeurs possibles qu’on obtienne après la phase d’analyse abstraire ont la même probabilité.
De même, on met toutes les valeurs de χl égales à
1
l
- c’est à dire toutes les expressions dans le programme ont la même probabilité
à exécuter.
Après 100 pas, on a le résultat suivant :
Pi_1((false)): 1.0
Pi_1((bottom)): 0 Pi_2((function 2)): 1
32
Pi_f((function 13)): 1.0 Pi_3((false)): 1.0 Pi_3((bottom)): 0
Pi_4((false)): 1 Pi_8((false)): 1.0 Pi_8((bottom)): 0
Pi_9((function 13)): 1.0 Pi_9((bottom)): 0 Pi_10((pair 10)): 1
Pi_10((bottom)): 0 Pi_11((false)): 1 Pi_12((false)): 1
Pi_13((function 13)): 1 Pi_x((pair 10)): 1.0 Pi_14((false)): 1.0
Pi_14((bottom)): 0 Pi_15((pair 10)): 1.0 Pi_15((bottom)): 0
chi1:0.08434709450448416 chi2: 0.08394209754223751
chi3:0.08279578902023832 chi4: 0.0823628782155107
chi8:0.08219011651595416 chi9: 0.08232266379931359
chi10:0.08272488772696303 chi11: 0.08329002758582212
chi12:0.08386778600929741 chi13: 0.0833738660462502
chi14:0.08430346984862165 chi15: 0.08447932318531196
Le résultat est bon en comparaison avec lequel en réalité.
33
Chapitre 6
Conclusion
Dans cette mémoire, nous proposons une nouvelle méthode de profiling
qui aide à donner les résultats d’évaluation d’un code source d’un programme
sans exécuter le programme. Nous résolvons le problème de profiling sans
exécution traitement en créant un système de contrainte qui modèle la ‘suite’
d’exécution du programme. En examinant ce système, on peut obtenir un
‘image’ sur le profiling du programme.
Plus précisément, notre méthode se divise en deux phases. La première
phase, on construit et résolve un système de contrainte des valeurs abstraites
pour obtenir une estimation abstraite sur le résultat d’exécution de toutes les
expressions dans le programme à analyser. La deuxième phase, on construit
un système d’équation qui simule la ’suite’ d’exécution du programme, puis
on applique une technique similaire à celle utilisée dans le réseaux neutron
pour trouver un point ‘stable’ de ce système. Ce point stable est considéré
comme un profilage de notre programme.
Les résultats préliminaires prouvent que, avec cette méthodologie, nous
pouvons obtenir un résultat ‘acceptable’ dans plusieurs cas.
D’autre part, dans quelques cas, le résultat n’est pas bon. Il y a une grande
différence entre le résultat de profiling et celui en réalité. Le résultat de la
première phase est sûr mais assez faible, tandis que le résultat de phase deux
est incertain parce que le système d’équation qu’on a construit ne converge
pas toujours quand on applique l’itération avec le taux d’apprentissage.
Une autre limitation, c’est que ce système, il ne modelé pas bien la situation où le programme ne retourne pas. Bien qu’on ait inclus Π(⊥) dans le
système, la valeur de Π(⊥) devient zéro rapidement. La raison est qu’on ne
propose pas les équations qui peuvent ajouter les nouvelles valeurs à Π(⊥).
Pour compléter ce projet, on doit surmonter ce problème. En regardant
le programme, on constate que, on peut créer la nouvelle valeur de Π(⊥)
basée sur la différence entre la suite d’entrée et la suite de sortie de chaque
34
expression parce que quand une expression ne retourne pas, il y a seulement
la suite d’entrée mais pas de suite de sortie.
35
Bibliographie
[1] Danny Dubé. Le contenu du cours de compilation et interprétation, 2006
[2] Danny Dubé. Le contenu du cours d’implantation et optimisation des
langages fonctionnels, 2006
[3] Susan L. Graham, Peter B. Kessler, et Marshall K. Mckusick gprof : A
Call Graph Execution Profiler Proceedings of the SIGPLAN ’82 Symposium on Compiler Construction,1982
[4] Amitabh Srivastava et Alan Eustace ATOM : A System for Building
Customized Program Analysis Tools Technical Report 94/2, Western
Research Lab, Compaq, Mar. 1994
[5] M.J. Maron. Numerical Analysis : A Practical Approach. Macmillan
Publishing Co., New York, 1987.
[6] J. E. Dennis and R. B. Schnabel. Numerical Methods for Unconstrained
Optimization and Nonlinear Equations. Prentice Hall Series in Computational Mathematics, Prentice Hall, Englewood Cliffs, N.J, 1983.
[7] John H. Mathews and Kurtis K. Fink. Numerical Methods Using Matlab.
Prentice-Hall Pub. Inc., Upper Saddle River, N.J, 2004.
[8] Martinez, J. M. Practical quasi-newton methods for solving nonlinear
systems. Journal of Computational and Applied Mathematics124(2000),
pages 97–122.
[9] Fokkema, Diederik R. ; Sleijpen, Gerard L. G. ; Van der Vorst, Henk A.
Accelerated inexact Newton schemes for large systems of nonlinear equations. SIAM J. Sci. Comput.19(1988) no. 2, pages 657–674, 1988.
36
Téléchargement