Rapport de stage de DEA 
´Evaluation de la quantit´e de travail utile 
dans l’ex´ecution des programmes 
Benjamin Vidal 
R...
Sujet de stage 
La recherche en architecture de processeur est confront´ee actuellement `a des contraintes qui 
rendent de...
Remerciements 
Au cours de ce stage au sein de l’´equipe CAPS de l’IRISA, il m’a ´et´e possible de rencontrer 
un grand no...
Table des mati`eres 
1 Bibliographie 9 
1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ....
1.5 Troisi`eme approche : 
Travail inutile global lors de l’ex´ecution d’un programme . . . . . . . . . . . . . . . . 16 
...
2.4 R´esultats & Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 
2.4.1 Les chiffres. ....
Table des figures 
1.1 Mise en ´evidence de l’inutilit´e des instructions ne produisant des r´esultats utilis´es 
que par ...
Liste des Algorithmes 
1 Construction du graphe de d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . 23 
2 Parc...
Chapitre 1 
Bibliographie 
1.1 Introduction 
Aujourd’hui, pour am´eliorer les performances d’un programme, il ne suffit pl...
1.2 Compilation et travail inutile 
1.2.1 Vous avez dit « instructions inutiles » ? 
Il peut paraˆıtre surprenant au premi...
1.3 Premi`ere approche : 
Instructions inutiles d´etect´ees dynamiquement 
1.3.1 Introduction 
Les auteurs de l’article [1...
donc tr`es important d’avoir une estimation la plus fine possible afin d’´eviter ce genre de cas et afin 
d’augmenter le n...
1.4 Deuxi`eme approche : 
´Ecritures silencieuses 
1.4.1 Introduction 
D’apr`es les auteurs de l’article [5], il existe pr...
1.4.3 Les cons´equences de l’´elimination des ´ecritures silencieuses 
Au del`a du gain ´evident que provoquerait un m´eca...
1.4.5 Conclusion sur cette approche 
Les auteurs de l’article [5] ont mis en ´evidence une grande quantit´e de travail inu...
1.5 Troisi`eme approche : 
Travail inutile global lors de l’ex´ecution d’un programme 
1.5.1 Introduction 
Dans cette appr...
Exemple simple : 
a := lire(); 
b := VRAI; 
c := 0; 
si a=0 faire 
b := FAUX; 
c := 5; 
fsi 
si b alors écrire(c) 
sinon é...
trouv´ees pour r´eduire cette quantit´e de travail inutile. Cependant, l’objet du stage reste celui-ci : 
« Concevoir et ´...
1.6 Conclusion 
En conclusion, nous pouvons dire que plusieurs mani`eres d’´eliminer le travail inutile ont d´ej`a ´et´e 
...
Chapitre 2 
Compte rendu du stage 
2.1 Introduction 
2.1.1 Le travail inutile, qu’est ce que c’est ? 
Le travail inutile e...
Exemple simple : 
instruction 1; 
si booléen faire 
instruction 2; 
instruction 3; 
fpour 
instruction 4; 
Dans cet exempl...
ce r´esultat (le r´esultat est un graphe ressemblant `a la figure 2.2 page 25). 
2.1.2 Notre protocole de test 
A partir d...
2.2 La m´ethode utilis´ee 
2.2.1 L’algorithme 
Pour construire notre graphe de d´ependance de donn´ee, nous avons choisi d...
possible de connaˆıtre les instructions utiles lors de l’ex´ecution d’un programme (d’apr`es la premi`ere 
d´efinition don...
39 
40 38 
36 
37 
35 
34 
3 
14 
33 
0 
13 
25 
24 
32 
31 
30 
29 
28 
26 
27 
23 
17 
16 
22 
21 
20 19 
18 
15 
9 
12 ...
2.2.2 L’optimisation 
Le gros probl`eme de cette approche est que le graphe devient tr`es rapidement ´enorme, mˆeme 
avec ...
de r´eduire notre graphe en supprimant les noeuds repr´esentant ce type d’instructions ainsi que leurs 
arcs sortants. 
Un...
1: mov 1,%indice 
boucle: 
2: mul %indice,10,%valeur 
3: store %valeur,[%indice+@tab-1] 
4: add %indice,1,%indice 
5: cmp ...
2.3 L’environnement de travail : Les choix de mise en oeuvre 
2.3.1 Les Outils 
Pour la mise au point de notre programme d...
mov r1,r2 
add r2,3,r2 
store r2,[a0] 
Code source 
mov r1,r2 
mov r2,tmp 
call fct 
add r2,3,r2 
mov r2,tmp 
call fct 
st...
inst1 
inst2 
inst3 
Code source 
sauvegarde 
call fct 
restauration 
inst1 
sauvegarde 
call fct 
restauration 
inst2 
sa...
2.3.4 SPARC : Le Meilleur des Mondes ? 
Le Sparc est une architecture RISC assez classique ce qui signifie que le nombre d...
delay slot de ce branchement doit ˆetre ex´ecut´ee ou non, le flot de contrˆole est aiguill´ee vers l’une 
ou l’autre des ...
2.3.5 S’affranchir de la num´erotation des registres faite par Salto 
Les registres du Sparc s’organisent en deux parties....
fois chaque argument r´ecup´er´e, il est possible d’acc´eder aux valeurs contenues dans les registres 
et aux ´eventuelles...
est ´ecrit par cette instruction. 
Cependant, dans le cas g´en´eral, il est n´ecessaire de d´etourner l’appel `a une telle...
2.4 R´esultats & Analyse 
2.4.1 Les chiffres. . . 
Une fois l’´evaluation de la quantit´e de travail inutile effectu´ee de...
somewhere : 
sub r3,r4,r5 
... 
mov r1,r2 
add r2,3,r2 
cmp 0,r2 
be somewhere 
store r2,(a0) 
... 
Code source 
mov r1,r2...
ex´ecutions qui ne sont alors plus ´equivalentes. Ceci se produit en raison d’un mauvais jugement 
port´e sur l’instructio...
108876.0 
90730.0 
72584.0 
54438.0 
36292.0 
18146.0 
0.0 
Compression d’un fichier RTF de 2127 octets 
Algorithme GZIP c...
ˆetre r´eellement inh´erent `a l’algorithme et non du `a une mauvaise impl´ementation de celui-ci. 
Exemple simple : 
Dans...
Ce qui signifie que lorsque la condition ci-dessus sera fausse, l’affectation de la variable prev match 
sera inutile (c’e...
l’´ecart entre les deux versions s’accentue dans le coeur de l’algorithme. En effet, durant la phase 
d’initialisation, le...
2.5 Conclusion 
Cette ´etude est, en premier lieu, une ´etude permettant de comprendre un ph´enom`ene, `a priori, 
contre ...
Bibliographie 
[1] G. Sohi A. Butt. Dynamic dead-instruction detection and elimination. ASPLOS X, October 
2002. 
[2] Jeff...
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Evaluation de la quantité de travail (in)utile dans l’exécution des programmes
Prochain SlideShare
Chargement dans…5
×

Evaluation de la quantité de travail (in)utile dans l’exécution des programmes

842 vues

Publié le

Le sujet proposé a pour but d’évaluer la quantité de travail réellement utile dans l’exécution des programmes. L’idée sous-jacente est que si une fraction importante de l’exécution d’un programme consiste en du travail inutile, il peut être intéressant de chercher un paradigme architectural permettant d’exploiter cette propriété.

Publié dans : Formation
0 commentaire
0 j’aime
Statistiques
Remarques
  • Soyez le premier à commenter

  • Soyez le premier à aimer ceci

Aucun téléchargement
Vues
Nombre de vues
842
Sur SlideShare
0
Issues des intégrations
0
Intégrations
12
Actions
Partages
0
Téléchargements
2
Commentaires
0
J’aime
0
Intégrations 0
Aucune incorporation

Aucune remarque pour cette diapositive

Evaluation de la quantité de travail (in)utile dans l’exécution des programmes

  1. 1. Rapport de stage de DEA ´Evaluation de la quantit´e de travail utile dans l’ex´ecution des programmes Benjamin Vidal Responsable de stage : Pierre Michaud Projet CAPS
  2. 2. Sujet de stage La recherche en architecture de processeur est confront´ee actuellement `a des contraintes qui rendent de plus en plus difficile l’augmentation des performances des processeurs. Ces contraintes sont multiples : consommation ´electrique, latence de propagation des signaux sur les connexions, temps et coˆut de developpement, etc. . . Pour esp´erer trouver d’´eventuelles solutions permettant d’augmenter les performances de mani`ere significative sur une large gamme d’applications, il faut trouver de nouveaux paradigmes d’architecture. Pour cela, il faut d’abord avoir une bonne compr´e-hension du comportement des programmes. Le sujet propos´e a pour but d’´evaluer la quantit´e de travail r´eellement utile dans l’ex´ecution des programmes. L’id´ee sous-jacente est que si une fraction importante de l’ex´ecution d’un programme consiste en du travail inutile, il peut ˆetre int´eressant de chercher un paradigme architectural per-mettant d’exploiter cette propri´et´e. Le probl`eme consiste `a donner une d´efinition de l’utilit´e d’un travail. Par exemple, dans la r´ef´erence [1], un r´esultat interm´ediaire est consid´er´e inutile s’il est ´ecrit dans un registre et est ´ecras´e sans avoir ´et´e utilis´e. Dans la r´ef´erence [5], un store `a une adresse m´emoire est consid´er´e inutile s’il ´ecrit une valeur ´egale `a la valeur d´ej`a stock´ee `a cette adresse. Nous proposons d’´etudier une autre d´efinition, selon laquelle une instruction dynamique est consid´er´ee utile si – Elle produit un r´esultat ´emis en sortie du programme (ex. printf) – Elle produit un r´esultat utilis´e comme op´erande d’une instruction utile – C’est un branchement dominant une instruction utile La partie recherche du stage consiste `a concevoir un algorithme efficace en temps et en m´emoire permettant d’´evaluer la quantit´e d’instructions dynamiques utiles. La partie mise-en-oeuvre consiste `a ´ecrire le programme correspondant, et `a l’utiliser pour obtenir des statistiques sur la fraction de travail utile, et d’autres statistiques, `a d´efinir, permettant de mieux appr´ehender le comportement des programmes. La mise en oeuvre se fera `a l’aide des outils d´evelopp´es au sein du projet CAPS. On travaillera sur des traces d’ex´ecution des programmes de la suite SPEC CPU2000. 2
  3. 3. Remerciements Au cours de ce stage au sein de l’´equipe CAPS de l’IRISA, il m’a ´et´e possible de rencontrer un grand nombre de personnes qui m’ont aid´e `a comprendre le fonctionnement d’un laboratoire de recherche en informatique et surtout `a acqu´erir le recul n´ecessaire pour mieux appr´ehender le monde de l’architecture des microprocesseurs. Je voudrais donc remercier Ronan Amicel, Laurent Bertaux, Fran¸cois Bodin, Henri-Pierre Charles, Assia Djabelkhir, Romain Dolbeau, Antony Fraboulet, Karine Heydemann, Thierry Lafage, Antoine Monsifrot, Laurent Morin, Gilles Pokam, Olivier Rochecouste, Andr´e Seznec et ´Eric Toullec. Je tiens aussi `a remercier Yannos Sazeides (Enseignant `a l’universit´e de Chypre) avec qui j’ai eu l’occasion d’´echanger des id´ees sur la fa¸con d’´elaborer automatiquement un graphe de d´ependance de donn´ee `a partir de l’ex´ecution d’un programme. Et enfin je tiens `a remercier tr`es chaleureusement mon maˆıtre de stage, Pierre Michaud, qui m’a donn´e la libert´e de travail que j’aurais aim´e trouver tout au long de mon exp´erience universitaire et professionnelle et m’a permis ainsi de suivre les pistes que je souhaitais. Je tiens ´egalement `a le remercier pour tous les conseils qu’il a pu me donner concernant le monde de la recherche (publique ou priv´ee) et de m’avoir fait partager sa vision des choses sur de nombreux sujets. 3
  4. 4. Table des mati`eres 1 Bibliographie 9 1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.2 Compilation et travail inutile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.2.1 Vous avez dit « instructions inutiles » ? . . . . . . . . . . . . . . . . . . . . . 10 1.2.2 Instructions statiques inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.3 Premi`ere approche : Instructions inutiles d´etect´ees dynamiquement . . . . . . . . . . . . . . . . . . . . . . 11 1.3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.3.2 Description du principe de d´etection et d’´elimination des instructions inutiles 11 1.3.3 Id´ees d’impl´ementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.3.4 Conclusion sur cette approche . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.4 Deuxi`eme approche : ´Ecritures silencieuses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.4.2 Le ph´enom`ene d’´ecriture silencieuse . . . . . . . . . . . . . . . . . . . . . . . 13 1.4.3 Les cons´equences de l’´elimination des ´ecritures silencieuses . . . . . . . . . . . 14 1.4.4 Id´ees d’impl´ementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.4.5 Conclusion sur cette approche . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4
  5. 5. 1.5 Troisi`eme approche : Travail inutile global lors de l’ex´ecution d’un programme . . . . . . . . . . . . . . . . 16 1.5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.5.2 Evaluer l’utilit´e d’une instruction ? . . . . . . . . . . . . . . . . . . . . . . . . 16 1.5.3 Mise en oeuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 1.5.4 Conclusion sur cette approche . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 1.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2 Compte rendu du stage 20 2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.1.1 Le travail inutile, qu’est ce que c’est ? . . . . . . . . . . . . . . . . . . . . . . 20 2.1.2 Notre protocole de test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.2 La m´ethode utilis´ee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.2.1 L’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.2.2 L’optimisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.2.3 Le r´esultat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 2.3 L’environnement de travail : Les choix de mise en oeuvre . . . . . . . . . . . . . . . . 29 2.3.1 Les Outils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.3.2 L’instrumentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.3.3 Le choix de la Plateforme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 2.3.4 SPARC : Le Meilleur des Mondes ? . . . . . . . . . . . . . . . . . . . . . . . . 32 2.3.5 S’affranchir de la num´erotation des registres faite par Salto . . . . . . . . . . 34 2.3.6 Les expressions r´eguli`eres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 2.3.7 La gestion des fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5
  6. 6. 2.4 R´esultats & Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.4.1 Les chiffres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.4.2 Le doute. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.4.3 La r´epartition du travail inutile . . . . . . . . . . . . . . . . . . . . . . . . . . 39 2.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3 Annexes 46 3.1 Petit historique du stage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.2 A propos de la description machine Salto du Sparc . . . . . . . . . . . . . . . . . . . 47 3.2.1 Gestion des instructions Save et Restore . . . . . . . . . . . . . . . . . . . . . 47 3.2.2 L’instruction call & link . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.2.3 L’instruction addx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.2.4 Un d´etail : les instructions nop, ba et bn . . . . . . . . . . . . . . . . . . . . . 47 3.3 R´esultat de l’´evaluation du travail inutile sur un exemple simple . . . . . . . . . . . 49 3.3.1 Code source en C de l’exemple . . . . . . . . . . . . . . . . . . . . . . . . . . 49 3.3.2 Code source en assembleur Sparc de l’exemple . . . . . . . . . . . . . . . . . 49 3.3.3 Identifiant d’instruction statique . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.3.4 Trace d’ex´ecution dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 3.3.5 Graphe de d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . . . . . . . 56 3.3.6 Trace d’ex´ecution dynamique 2 . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.4 Exemple de donn´ees stock´ees en cours d’ex´ecution . . . . . . . . . . . . . . . . . . . 59 3.5 Code source du programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 6
  7. 7. Table des figures 1.1 Mise en ´evidence de l’inutilit´e des instructions ne produisant des r´esultats utilis´es que par des instructions inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.1 La structure de donn´ee d’un noeud du graphe . . . . . . . . . . . . . . . . . . . . . . 24 2.2 Exemple de graphe g´en´er´e par l’algorithme 1 & 3 . . . . . . . . . . . . . . . . . . . . 25 2.3 Du code source en langage de haut niveau au graphe de d´ependance de donn´ee dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.4 Instrumentation de code source en assembleur . . . . . . . . . . . . . . . . . . . . . . 30 2.5 Principe de l’instrumentation faite par le programme d’´evaluation de la quantit´e de travail inutile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 2.6 Le principe de la fenˆetre de registres du Sparc . . . . . . . . . . . . . . . . . . . . . . 33 2.7 Quantit´e d’instructions assembleurs inutiles lors de l’ex´ecution de gzip dans diff´erentes conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.8 Mise en ´evidence d’un probl`eme d’impl´ementation par divergence du flot de contrˆole 38 2.9 ´Evolution de la quantit´e de travail inutile en fonction du temps . . . . . . . . . . . . 40 3.1 Graphe d’exemple g´en´er´e par l’utilitaire « dot » . . . . . . . . . . . . . . . . . . . . . 56 3.2 Les structures de donn´ees utilis´ees par le programme pour construire le graphe de d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7
  8. 8. Liste des Algorithmes 1 Construction du graphe de d´ependance de donn´ee . . . . . . . . . . . . . . . . . . . 23 2 Parcours du graphe (Noeud) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3 D´etection des instructions inutiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 8
  9. 9. Chapitre 1 Bibliographie 1.1 Introduction Aujourd’hui, pour am´eliorer les performances d’un programme, il ne suffit plus seulement d’ajou-ter du mat´eriel dans un syst`eme donn´e. Il faut avant tout ´etudier le comportement de ce programme afin d’adapter au mieux les ajouts qui doivent ˆetre faits au syst`eme. De ce constat, les architectes des microprocesseurs ont tir´e des id´ees aujourd’hui fondamentales (tels les diff´erents niveaux de m´emoires caches qui exploitent la propri´et´e de localit´e temporelle et spatiale d’acc`es aux donn´ees dans les programmes). En ce sens, certains travaux de recherche s’int´eressent aujourd’hui au probl`eme du travail ef-fectu ´e inutilement par un microprocesseur. Ils mettent en ´evidence une quantit´e non n´egligeable de travail inutile. Dans cette bibliographie, trois approches principales de travail inutile ont ´et´e retenues. 1. Une instruction produisant un r´esultat jamais utilis´e par une autre instruction est consid´er´ee comme inutile (approche de « l’instruction morte » retenue par l’article [1]). 2. Une instruction d’´ecriture est consid´er´ee comme inutile si cette derni`ere ne modifie pas l’´etat de la m´emoire (i.e. la mˆeme valeur est ´ecrite `a la mˆeme adresse m´emoire) (approche de « l’´ecriture silencieuse » retenue dans de nombreux articles [5, 3, 7]). 3. Une instruction est consid´er´ee comme utile si elle produit un r´esultat en sortie (affichage d’un r´esultat par exemple) ou qu’elle est elle mˆeme utile `a une instruction utile (approche retenue pour le stage). Apr`es un bref tour d’horizon des travaux d´ej`a effectu´es dans le domaine au niveau des compila-teurs, chacun des trois aspects d´ecrits ci-dessus du travail inutile sera d´evelopp´e dans un paragraphe de cette bibliographie. S’en suivra un paragraphe de discussion sur la possibilit´e de mˆeler ces deux approches pour essayer d’obtenir une coop´eration compilation/ex´ecution dans l’´elimination des instructions inutiles. 9
  10. 10. 1.2 Compilation et travail inutile 1.2.1 Vous avez dit « instructions inutiles » ? Il peut paraˆıtre surprenant au premier abord d’entendre parler de travail inutile dans un pro-gramme. En effet, `a partir du moment ou le programmeur demande d’effectuer un travail `a la machine, (encore que celui-ci ne soit pas infaillible. . .) ce travail doit avoir une utilit´e (au sens informatique du terme bien entendu. . .). Cependant, au del`a du programmeur, il existe toute une chaˆıne de m´ecanismes permettant de passer du langage de haut niveau (i.e. langage de program-mation classique) au code machine ex´ecutable. Ainsi ce programme va passer par toute sortes de transformations qui vont introduire du travail inutile. De plus, il est possible de trouver, dans la fa¸con dont sont con¸cus les programmes, du travail inutile (redondance de calculs par exemple). 1.2.2 Instructions statiques inutiles Pour commencer, il est important de rappeler ce que sont les instructions statiques et les ins-tructions dynamiques. Une instruction statique est une instruction telle qu’on peut la trouver dans le code source d’un programme. Une instruction dynamique est une instance d’instruction statique. A chaque instruction statique peut correspondre plusieurs instructions dynamiques (autant que de fois o`u l’on ex´ecute cette instruction statique). Exemple simple : pour i de 1 à n faire t[i] := 0; fpour Instruction statique : t[i] := 0 Instructions dynamiques associées : t[1] := 0, t[2] := 0, …, t[n] := 0 Dans l’exemple suivant, il est important de noter que si n n’est pas fix´e lors de la compilation, la seule connaissance du compilateur est l’instruction statique. Il ne pourra donc pas, `a priori se servir de la valeur de n `a des fins d’optimisation. Supposons maintenant qu’un programme ne soit compos´e que des instructions de l’exemple et n’affiche aucun r´esultat. Le compilateur peut en d´eduire que l’ensemble du travail `a effectuer pour ex´ecuter cette boucle est inutile. Cependant, il suffit d’ajouter une instruction qui utilise t[m] en lecture (m ´etant un param`etre d’entr´ee du programme inconnu `a la compilation) pour que, potentiellement, l’ensemble du travail de la boucle devienne utile. En effet, le compilateur ne sachant pas quelle case du tableau t va ˆetre acc´ed´e, il est oblig´e de consid´erer que l’ensemble de la boucle fournit du travail utile. Il existe de nombreuses autres mani`eres d’´eliminer du travail inutile lors de la compilation [2, 4] que nous n’aborderons pas ici car seul l’aspect d´ecrit ci-dessus se rapproche des travaux vis´es dans cette ´etude. Dans la suite de cette bibliographie, nous ne nous int´eresserons qu’aux instructions inutiles dynamiques (i.e. qui ne peuvent pas ˆetre d´etect´ees par le compilateur puisqu’elles d´ependent de valeurs d’entr´ees du programme non connues au moment de la compilation). 10
  11. 11. 1.3 Premi`ere approche : Instructions inutiles d´etect´ees dynamiquement 1.3.1 Introduction Les auteurs de l’article [1] se sont aper¸cus que le r´earrangement des instructions fait par les com-pilateurs lors des phases d’optimisations cr´e´e des instructions inutiles. En effet, comme le montre leurs r´esultats, une compilation faite sans optimisations montre un niveau faible d’instructions in-utiles alors qu’une compilation avec un fort niveau d’optimisation montre un taux d’instructions inutiles relativement ´elev´e (parfois sup´erieur `a 10 %). Cependant, malgr´e ce travail effectu´e inuti-lement, il est bon de rappeler que globalement, le temps d’ex´ecution de ces programmes diminue (i.e. on a bien l’effet d´esir´e). La question qui vient alors est : « Comment conserver ces optimisations tout en r´eduisant le travail inutile qui leur est associ´e ? » 1.3.2 Description du principe de d´etection et d’´elimination des instructions inutiles Dans un premier temps, l’important est d’analyser les instructions ex´ecut´ees inutilement afin de savoir comment les d´etecter. Les auteurs de l’article [1] se sont ainsi aper¸cus que les instructions dynamiques inutiles ´etaient tr`es souvent des instances d’un nombre r´eduit d’instructions statiques. Ces instructions statiques sont appel´ees des instructions partiellement inutiles. En marquant ces instructions particuli`eres comme ´etant propices `a g´en´erer des instructions dynamiques inutiles, il est possible de ne faire un traitement particulier que sur ces derni`eres afin de savoir si une instance pr´ecise sera r´eellement inutile. Lors de l’ex´ecution, pour chaque instance d’une instruction partiellement inutile, une estimation de l’utilit´e de cette instruction dynamique sera faite. De cette estimation d´ecoulera son ex´ecution ou non. Dans le cas d’une mauvaise pr´ediction, un m´ecanisme de r´ecup´eration permet de lancer l’ex´ecution de cette instruction au moment ou l’on apprend que la pr´ediction est ´erron´ee. 1.3.3 Id´ees d’impl´ementation Les auteurs de l’article [1] ont donn´e quelques id´ees d’impl´ementation qui pourraient ˆetre mises en oeuvre pour la d´etection de ce type d’instructions. La plus simple consiste `a m´emoriser dans un cache totalement associatif les instructions statiques ayant d´ej`a g´en´er´e des r´esultats inutiles par le pass´e. Du fait qu’un faible nombre de ces instructions g´en`erent un grand nombre des instructions dynamiques inutiles, ce cache permettra de « suspecter » la prochaine instance d’une instruction statique ayant d´ej`a g´en´er´e des r´esultats inutiles. Par la suite, lors de la d´etection d’une instruction dynamique « suspect´ee » d’ˆetre inutile, son ex´ecution sera suspendue jusqu’au « verdict » final permettant de savoir si il ´etait juste de la sus-pecter. Si tel est le cas, cette instruction ne sera pas ex´ecut´ee, dans le cas contraire, cette instruction sera ex´ecut´ee ajoutant ainsi un surcoˆut dˆu au retard d’ex´ecution pris par cette instruction. Il est 11
  12. 12. donc tr`es important d’avoir une estimation la plus fine possible afin d’´eviter ce genre de cas et afin d’augmenter le nombre d’instruction inutiles suspect´ees `a juste titre. Pour cela, des optimisations sont propos´ees : utilisation de l’information de flot de contrˆole et ajout d’un compteur deux bits `a saturation principalement. Il est important de noter que ces impl´ementations ne tiennent pas compte des r´esultats calcul´es qui ne servent qu’`a des instructions inutiles. Autrement dit, cette impl´ementation ne prend pas en compte le caract`ere transitif que peuvent avoir certaines instructions inutiles. Instruction produisant un résultat R Instruction utilisant R et produisant R’ Instruction utilisant R’ et produisant R’’ Si R’’ est un résultat inutile R’ et R auront été produits inutilement Fig. 1.1 – Mise en ´evidence de l’inutilit´e des instructions ne produisant des r´esultats utilis´es que par des instructions inutiles 1.3.4 Conclusion sur cette approche En conclusion, nous pouvons dire que les auteurs de l’article [1] ont mis en ´evidence une quantit´e non n´egligeable de travail inutile mˆeme si elle reste, aujourd’hui, difficile `a exploiter. En effet, dans un environnement o`u les ressources sont peu limit´ees, l’efficacit´e de l’impl´ementation d´ecrite ci-dessus offre des gains en performance n´egligeables. En revanche, dans des conditions de ressources plus limit´ees, les gains peuvent atteindre 10 % d’am´elioration des performances. De plus le fait d’ex´ecuter moins d’instructions permet une diminution de la charge des Unit´es Arithm´etiques et Logiques (UAL) et de la consommation ´electrique relativement importante. D’apr`es les auteurs, un m´ecanisme mat´eriel diminuant l’impact des instructions inutiles sur la performance et la consom-mation ´electrique permettrait d’appliquer des optimisations de code plus pouss´ees `a la compilation. 12
  13. 13. 1.4 Deuxi`eme approche : ´Ecritures silencieuses 1.4.1 Introduction D’apr`es les auteurs de l’article [5], il existe principalement deux types d’´ecritures silencieuses. D’une part les mises `a jours de valeurs silencieuses (qui ne changent pas l’´etat de la m´emoire dans laquelle elles ´ecrivent) et d’autre part les ´ecritures silencieuses stochastiques qui mettent `a jour la m´emoire de mani`ere pr´evisible. Dans la suite de ce chapitre, nous nous concentrerons sur les mises `a jour de valeurs silencieuses et parlerons, par abus de langage, d’´ecritures silencieuses pour les d´esigner. 1.4.2 Le ph´enom`ene d’´ecriture silencieuse Au vu de la d´efinition de ce qu’est une ´ecriture silencieuse, il parait difficile de croire que ces instructions puissent avoir un impact n´egatif important sur les performances d’un programme. Pourtant, les articles sur le sujet montrent que souvent plus de 30 % des ´ecritures sont silencieuses dans les applications test´ees. En effet, il existe de nombreux cas o`u, lors du parcours des ´el´ements d’un tableau, les modifications apport´ees par ce parcours ne concernent qu’un petit nombre des ´el´ements de ce tableau. Exemples simples : pour i de 1 à n faire b := (b & t[i]); fpour pour i de 1 à n faire t[i] := (t[i] & b); fpour (a) (b) (c) t étant un tableau de booléens b étant un booléen l'opération & étant un "ET" logique pour i de 1 à n faire t[i] := t[i] + e(i); fpour t étant un tableau d'entiers e étant une fonction Dans l’exemple (a), nous pouvons voir que pour chaque case du tableau t dont le bool´een est `a vrai, l’ex´ecution du corps de la boucle ne produit aucun travail utile (la mˆeme valeur sera r´e-´ecrite dans b). Dans l’exemple (b), le simple fait que b soit ´egal `a vrai entraˆıne une inutilit´e de l’ensemble de la boucle. Dans l’exemple (c), lorsque "(i) renvoi z´ero, le corps de la boucle peut-ˆetre consid´er´e comme inutile. Un autre cas assez fr´equent de travail inutile est celui o`u un tableau est initialis´e apr`es avoir ´et´e utilis´e une premi`ere fois. Si lors de la premi`ere utilisation de ce tableau, toutes ses valeurs n’ont pas ´et´e modifi´ees, il est inutile de r´e-initialiser l’ensemble des cases de ce tableau. Il existe d’autres situations dans lesquelles un grand nombre d’´ecritures silencieuses peuvent ˆetre observ´ees : lors de l’appel d’un sous-programme, si les registres sauvegard´es n’ont pas ´et´e utilis´es dans ce sous-programme, leur restauration sera inutile. Ce mˆeme ph´enom`ene peut-ˆetre observ´e lors de la sauvegarde/restauration de contexte d’un processus par un syst`eme d’exploitation. 13
  14. 14. 1.4.3 Les cons´equences de l’´elimination des ´ecritures silencieuses Au del`a du gain ´evident que provoquerait un m´ecanisme fiable de suppression des ´ecritures silencieuses, un tel syst`eme permettrait ´egalement de supprimer une certaine quantit´e de travail assez importante li´ee `a ces instructions. En premier lieu, les informations de contrˆole li´ees `a ces instructions ne sont plus n´ecessaires (Ex : si une s´erie d’´ecritures silencieuses se trouve dans une boucle, il est inutile d’ex´ecuter la boucle). De plus, lors de l’ex´ecution d’une instruction de range-ment en m´emoire, tout un m´ecanisme lourd de rapatriement de la ligne de cache concern´ee vers la m´emoire est mis en place (´ecriture de la donn´ee dans le cache, marquage de la ligne de cache comme ´etant modifi´ee puis, lors du chargement de nouvelles donn´ees dans cette ligne de cache, ´ecriture de l’ancienne ligne de cache consid´er´ee comme modifi´ee en m´emoire). De fait, la suppression d’une ´ecriture ´evite d’avoir `a passer par toute ces op´erations d’acc`es `a la m´emoire tr`es coˆuteuses. Comme expliqu´e dans l’article [3], cette remarque prend encore plus d’importance dans un syst`eme multi-processeur puisque `a chaque ´ecriture m´emoire est associ´e un message d’invalidation `a destination des autres processeurs provoquant un d´efaut de cache lors du prochain acc`es `a ces donn´ees. . . Il est ´egalement important de noter que si certaines ´ecritures ne sont pas effectu´ees, de fait, certaines d´ependances de donn´ees n’existent plus. De cette fa¸con, le processeur n’est plus oblig´e d’attendre que ces valeurs soient ´ecrites pour pouvoir les utiliser. Le rendement du pipeline du processeur est alors am´elior´e. 1.4.4 Id´ees d’impl´ementation Les auteurs de l’article [5] ont propos´e une impl´ementation basique permettant de supprimer les ´ecritures silencieuses. Cette impl´ementation consiste `a remplacer toute les op´erations de ran-gement en m´emoire par trois op´erations : Chargement de l’ancienne valeur pr´esente en m´emoire, comparaison avec la valeur qui doit y ˆetre ´ecrite et enfin, dans le cas o`u ces deux valeurs ne seraient pas ´egales, ´ecriture de la nouvelle valeur en m´emoire. Cette m´ethode est sˆure et permet de d´etecter l’ensemble des ´ecritures silencieuses. De plus, les lectures pouvant ˆetre servies en parall`eles, il peut ˆetre int´eressant de remplacer les ´ecritures par des lectures suivies de comparaisons. Cependant, dans la mesure ou le nombre d’´ecritures silencieuses ne repr´esente pas la majorit´e des ´ecritures m´emoire, les auteurs ont ajout´e une « impl´ementation parfaite » dans laquelle un m´ecanisme per-met de savoir si une ´ecriture va ˆetre utile et, dans ce cas, n’effectuera que l’´ecriture en m´emoire sans avoir `a comparer la nouvelle valeur `a la valeur pr´ec´edente. D’autres id´ees d’impl´ementations apparaissent ´egalement dans l’article [5] comme par exemple la possibilit´e que la ligne de cache ne soit pas marqu´ee comme modifi´ee lorsqu’elle re¸coit une ´ecriture silencieuse ´evitant ainsi d’avoir `a propager l’´ecriture en m´emoire centrale (avantage principal de l’´elimination des ´ecriture silencieuses). L’impl´ementation retenue pour les simulations faites par les auteurs de [5] est la premi`ere propos´ee avec pour caract´eristique suppl´ementaire que seules les ´ecritures mises en attente vont subir une v´erification de leur utilit´e. Ce qui veut dire qu’une ´ecriture survenant `a un moment o`u au moins un port d’´ecriture de la m´emoire est disponible sera servie avant que la v´erification de son utilit´e n’ai pu ˆetre faite. De cette fa¸con, les performances des ´ecritures ne sont jamais d´egrad´ees puisque le m´ecanisme n’agit que sur la file d’attente des ´ecritures afin de la r´eduire. 14
  15. 15. 1.4.5 Conclusion sur cette approche Les auteurs de l’article [5] ont mis en ´evidence une grande quantit´e de travail inutile `a travers les ´ecritures silencieuses. En effet, les proportions d’´ecritures silencieuses obtenues lors des tests sont parfois tr`es importantes et laissent penser qu’elles pourraient avoir une influence tr`es importante sur les performances, notamment dans les syst`emes dont la purge des lignes de cache en m´emoire est un goulet d’´etranglement. Les auteurs mettent ´egalement en avant la r´eduction du trafic sur le bus d’un syst`eme multiprocesseur `a m´emoire partag´ee qui est souvent un point critique dans ce type de syst`emes (ce trafic limite le nombre de processeurs sur un mˆeme bus). D’autres travaux ´elargissant le th`eme de l’´ecriture silencieuse ont ´egalement ´et´e pr´esent´es comme celui sur les ´ecritures silencieuses temporaires [6] consid´erant que si une valeur en m´emoire est modifi´ee puis remise `a son ancienne valeur et qu’aucune lecture ne soit intervenue sur la valeur transitoire, elle peut-ˆetre consid´er´ee comme silencieuse. Ce mod`ele semble bien s’adapter aux cas d´ecrits ci-dessus de sauvegarde et de restauration de contexte fr´equents (appel de sous-programmes, passage d’un processus `a un autre. . .). 15
  16. 16. 1.5 Troisi`eme approche : Travail inutile global lors de l’ex´ecution d’un programme 1.5.1 Introduction Dans cette approche, la probl´ematique est un peu diff´erente de celle vue dans les deux premiers paragraphes. En effet, le but de ces deux approches ´etait de d´etecter (soit par pr´evision, soit de mani`ere dynamique) une cat´egorie d’instructions inutiles afin d’´eviter leur ex´ecution « au vol ». Dans l’approche retenue pour le stage, il s’agit d’abord de regarder quelle est la quantit´e de travail inutile de fa¸con globale (essayer d’´evaluer l’ensemble du travail fait inutilement par un programme) afin d’avoir ensuite une id´ee du type de comportement ou d’application ex´ecut´e par un processeur qui produit le plus de travail inutile. De cette fa¸con, si certains r´esultats montrent une quantit´e non n´egligeable de travail inutile dans certains types d’applications, il sera ensuite possible d’´etudier pourquoi ce travail inutile est si important et si il peut ˆetre ´evit´e d’une mani`ere ou d’une autre. 1.5.2 Evaluer l’utilit´e d’une instruction ? La m´ethode retenue ici pour ´evaluer l’utilit´e d’une instruction est assez simple : Une valeur qui est affich´ee en sortie d’un programme est consid´er´ee comme un r´esultat utile. Toute instruction ayant servi `a calculer ce r´esultat est une instruction utile. Ainsi, les instructions utiles `a un r´esultat peuvent ˆetre repr´esent´ees par un arbre de d´ependance entre ces derni`eres dont la racine est le r´esultat lui-mˆeme et chaque noeud repr´esente les instructions utiles au calcul de ce r´esultat (aussi bien les instructions de rangement/r´ecup´eration en m´emoire, de calcul et de branchement). Les feuilles seront alors les valeurs d’entr´ees (param`etres fix´es lors de la compilation ou de l’ex´ecution) du programme. En regroupant l’ensemble des arbres ainsi obtenus pour chaque r´esultat en un graphe orient´e dont les sources sont les r´esultats et les puits sont les valeurs d’entr´ee du programme, il est possible d’identifier quelles sont les instructions r´eellement utiles au programme. En effet, les instructions et les valeurs d’entr´ees inutiles au programme n’appartiendront pas `a ce graphe et seront ainsi mises en ´evidence. 16
  17. 17. Exemple simple : a := lire(); b := VRAI; c := 0; si a=0 faire b := FAUX; c := 5; fsi si b alors écrire(c) sinon écrire(a) Exemple de graphe d’exécution si a vaut 0 : écrire(a) nécessite b b := FAUX branchement correspondant Test a=0 nécessite a nécessite a a := lire() Valeur d’entrée du programme a := lire() Valeur d’entrée du programme c := 5 c := 0 b := VRAI Instructions exécutées inutilement Exemple de graphe d’exécution si a vaut 1 : nécessite b nécessite c Test a=0 nécessite a a := lire() Valeur d’entrée du programme écrire(c) c := 0 Valeur d’entrée du programme Aucune instruction n’est exécutée inutilement b := VRAI Valeur d’entrée du programme Cet exemple permet de mettre en ´evidence le fait que selon les valeurs d’entr´ees du programme, il peut y avoir du travail inutile ou pas. De plus, il met en lumi`ere (dans le cas o`u a vaut 1) le fait que le test a=0 correspondant au branchement du « si » doit ˆetre pris en compte comme ´etant du travail utile puisque de ce branchement vont d´ependre les instructions qui vont suivre (Nous pouvons dire que ces instructions « exigent » l’ex´ecution de ce branchement et donc du test qui permet de savoir si ce branchement doit ˆetre pris). Cette m´ethode d’identification des instructions inutiles semble parfaite (mˆeme si elle ne prend pas en compte certaines ´ecritures silencieuses). Cependant, elle n´ecessite le d´eroulement complet du programme afin de savoir si oui ou non une instruction dynamique du programme sera utile pour un r´esultat final. De fait, cette m´ethode ne peut pas ˆetre utilis´ee directement pour ´eliminer « au vol » les instructions inutiles. En revanche, elle permet d’exhiber de nombreux cas d’instructions inutiles que les autres m´ethodes ne d´etectent pas. Par exemple, le cas d’une instruction inutile par transitivit´e mis en ´evidence figure 1.1 sera d´etect´e par cette m´ethode. 1.5.3 Mise en oeuvre Comme d´ecrit dans le sujet de stage, la mise en oeuvre de cette approche du travail inutile consis-tera `a ´elaborer un programme permettant de d´etecter les instructions dynamiques inutiles, d’apr`es la d´efinition donn´ee ci-dessus, afin de faire des statistiques sur la quantit´e de travail inutile dans un ensemble de programmes `a tester. Diff´erentes cat´egories de travail inutile pourront ´egalement ˆetre mises en ´evidence (Ex : chargements inutiles, rangements en m´emoire inutiles, calculs inutiles. . .). Une fois les tests effectu´es, un travail de regroupement des applications test´ees selon les r´esultats pourra ˆetre fait afin de d´egager, ´eventuellement, des « motifs » de comportements permettant en-suite de savoir quelles applications sont le plus concern´ees par quel type de travail inutile. Nous pouvons imaginer, `a partir de l`a, que des ´ebauches de solutions mat´erielles et/ou logicielles ne soient 17
  18. 18. trouv´ees pour r´eduire cette quantit´e de travail inutile. Cependant, l’objet du stage reste celui-ci : « Concevoir et ´ecrire un programme permettant de calculer la quantit´e de travail inutile dans un programme particulier apr`es son ex´ecution » . Dans ce sens, le travail `a effectuer en stage sera, dans un premier temps, de r´efl´echir `a la mani`ere de d´etecter quelles sont les instructions qui ont ´et´e ex´ecut´ees inutilement lorsque l’ex´ecution d’un programme sera termin´ee (algorithme de construction puis d’exploration du graphe de d´ependance des instructions d´ecrit dans cette section). Ces r´esultats devront ensuite ˆetre mis en forme afin de d´egager des statistiques sur la quantit´e de travail inutile (pourcentage d’instructions inutiles) et sur la nature de ces instructions (de quel type d’instructions s’agit-il ?). Une fois cet algorithme impl´ement´e, il sera int´eressant de le tester sur diff´erent type de programme afin de savoir quelle est la quantit´e de travail r´eellement inutile (d’apr`es la d´efinition donn´ee en introduction de cette section) dans ces programmes. 1.5.4 Conclusion sur cette approche En conclusion nous pouvons dire que l’approche retenue pour le stage est une d´emarche scien-tifique exp´erimentale permettant de savoir quelle est la proportion globale de travail inutile dans un programme. Si les r´esultats r´ev`elent une grande quantit´e de travail inutile, de nombreuses ouvertures paraissent possibles : D´etection de ces instructions grˆace `a des compilateurs « intel-ligents », d´etection de ces instructions « au vol » (approche d´ej`a retenue par [1]), coop´eration compilateur/mat´eriel ou encore ajout de nouvelles instructions afin de faciliter leur d´etection. 18
  19. 19. 1.6 Conclusion En conclusion, nous pouvons dire que plusieurs mani`eres d’´eliminer le travail inutile ont d´ej`a ´et´e abord´ees (tant dans le domaine de la compilation qu’en architecture). En effet, lors de la compila-tion, une certaine quantit´e de travail inutile peut d´ej`a ˆetre supprim´ee (en fonction des informations que le compilateur peut exploiter). Cependant, nous avons ´egalement vu que certaines optimisa-tions de ces mˆemes compilateurs g´en`erent des instructions inutiles. De fait, diff´erentes m´ethodes ont ´et´e propos´ees pour ´eliminer ce travail inutile lors de l’ex´ecution (instructions dynamiques inutiles). Une autre approche int´eressante consistait `a ´eliminer les ´ecritures silencieuses, une autre forme de travail inutile. Apr`es ce tour d’horizon global, il est assez difficile de savoir de mani`ere pr´ecise quelle est la quantit´e de travail inutile effectu´ee par un microprocesseur lors de l’ex´ecution d’un programme. C’est `a cette question que va tenter de r´epondre le travail `a venir en stage. . . 19
  20. 20. Chapitre 2 Compte rendu du stage 2.1 Introduction 2.1.1 Le travail inutile, qu’est ce que c’est ? Le travail inutile est une notion difficile `a cerner. Il existe diff´erentes approches pour traiter le probl`eme du travail effectu´e inutilement par un programme. Tout d’abord, le travail inutile peut- ˆetre de nature statique (d´etectable et supprimable lors de la compilation) ou de nature dynamique (visible uniquement lors de l’ex´ecution). L’´elimination du travail inutile statique est d´ej`a bien connue et fait partie int´egrante de toute chaˆıne de compilation optimis´ee digne de ce nom. Ici nous nous int´eresserons seulement au travail inutile de nature dynamique puisque ce dernier n’est pas exploit´e par les processeurs ou les langages de programmation actuels. Une fois le cadre du travail inutile dynamique pos´e, il est n´ecessaire de se donner une d´efinition pr´ecise du travail inutile afin de pouvoir en ´evaluer la quantit´e lors de l’ex´ecution d’un programme sur un jeu de donn´ees particulier de fa¸con automatique. Cette d´efinition, dans un premier temps tr`es large, a ´et´e restreinte pour des raisons d’impl´ementation. La d´efinition prise comme base de d´epart `a cette ´evaluation ´etait la suivante : Tout travail qui ne sert, ni directement, ni indirectement, `a produire un r´esultat est jug´e inutile. De fa¸con plus pr´ecise : Une instruction dynamique est consid´er´ee comme utile si - Elle produit un r´esultat ´emis en sortie du programme (ex : affichage `a l’´ecran). - Elle produit un r´esultat utilis´e comme op´erande d’une instruction utile. - C’est un branchement dominant une instruction utile. 20
  21. 21. Exemple simple : instruction 1; si booléen faire instruction 2; instruction 3; fpour instruction 4; Dans cet exemple, le branchement conditionnel « domine » les instructions 2 et 3. Si le bool´een est vrai et que les instructions 2 et 3 sont inutiles, alors on peut consid´erer le branchement comme inutile. Note Par abus de langage, dans la suite de ce document, nous d´esignerons toutes les instructions de transfert de contrˆole (branchement conditionnels et inconditionnels, sauts, appels de fonctions. . .) par l’expression « instruction de branchement ». En regardant cette d´efinition de plus pr`es, un probl`eme se pose dans le cas g´en´eral : Supposons que l’instruction 2 soit un « store » qui range une valeur `a une adresse m´emoire, que l’instruction 4 soit un « load » et que le bool´een soit `a faux. Dans ce cas, si l’instruction 4 est consid´er´ee comme utile et si les deux acc`es pointent sur la mˆeme adresse m´emoire, alors le branchement devra ˆetre consid´er´e comme utile. Cependant, l’adresse de l’acc`es `a la m´emoire qui aurait pu ˆetre fait par l’instruction 2 n’est pas connue puisque cette instruction n’a pas ´et´e ex´ecut´ee. A cause de ce type d’acc`es `a la m´emoire (dont l’adresse acc´ed´ee n’est connue qu’`a l’ex´ecution) nous avons du faire l’hypoth`ese conservatrice suivante : Toutes les instructions de branchements sont consid´er´ees comme utiles. Ce qui nous donne la d´efinition suivante : Une instruction dynamique est consid´er´ee comme utile si - Elle produit un r´esultat ´emis en sortie du programme (ex : affichage `a l’´ecran). - Elle produit un r´esultat utilis´e comme op´erande d’une instruction utile. - C’est un branchement. Note Par abus de langage, dans la suite de ce document, nous utiliserons le mot « ressource » pour d´esigner soit un registre soit un emplacement m´emoire. Partant de cette nouvelle d´efinition, l’objectif ´etait de construire un graphe de d´ependance de donn´ee en reliant les instructions lisant une ressource `a la derni`ere instruction ayant ´ecrit dans cette mˆeme ressource. De cette fa¸con, lorsqu’une ressource apparaˆıt comme utile (lorsque sa valeur est ´ecrite en sortie ou qu’elle est utilis´ee par une instruction de branchement), il devient possible, en parcourant les arcs de ce graphe, de trouver toutes les instructions qui ont ´et´e utiles pour produire 21
  22. 22. ce r´esultat (le r´esultat est un graphe ressemblant `a la figure 2.2 page 25). 2.1.2 Notre protocole de test A partir de cette d´efinition, nous avons essay´e de mesurer la quantit´e de travail inutile dans des petits programmes d’exemple, puis, une fois ces exemples valid´es, nous avons test´e notre protocole pour mesurer le travail inutile sur un programme plus cons´equent et surtout n’ayant pas ´et´e con¸cu dans le but d’en mesurer la quantit´e de travail inutile. Pour faire nos tests, nous avons choisi l’utilitaire de compression/d´ecompression de donn´ees gzip. 22
  23. 23. 2.2 La m´ethode utilis´ee 2.2.1 L’algorithme Pour construire notre graphe de d´ependance de donn´ee, nous avons choisi d’instrumenter chaque instruction assembleur afin de contrˆoler de fa¸con pr´ecise les entr´ees et les sorties de chacune d’elles. Les entr´ees ´etant les op´erandes d’une instruction (les ressources ´etant lues par l’instruction) et les sorties ´etant les r´esultats d’une instruction (les ressources ´etant ´ecrites par l’instruction). Dans le cas g´en´eral, notre impl´ementation de la d´etection de d´ependance de donn´ee peut se r´esumer par l’algorithme 1. Algorithme 1: Construction du graphe de d´ependance de donn´ee Entr´ee : Programme dont on veut ´evaluer la quantit´e de travail inutile. Donn´ees `a fournir en entr´ee `a ce programme. Sortie : Graphe de d´ependance de donn´ee dynamique. 1 pour chaque instruction ex´ecut´ee faire 2 Cr´eer une repr´esentation interne de cette instruction; 3 L’ajouter `a la liste des instructions dynamiques ex´ecut´ees; {Cette repr´esentation interne contient des informations concernant l’instruction : son num´ero dynamique, le fichier source auquel elle appartient, son num´ero statique dans ce fichier, son type. . . } 4 pour chaque op´erande de l’instruction {ressource lue} faire 5 Lire dans la table des ressources quelle est la derni`ere instruction qui a ´ecrit dans cette ressource; 6 Cr´eer un lien de d´ependance {arc dans le graphe} entre l’instruction courante et la derni`ere instruction ayant ´ecrit dans la ressource en question; {Associe `a l’op´erande le num´ero de l’instruction dynamique qui l’a produit} fin 7 pour chaque r´esultat de l’instruction {ressource ´ecrite} faire 8 Ecrire dans la table des ressources que l’instruction courante a modifi´ee l’´etat de cette ressource; fin fin Une fois cet algorithme ex´ecut´e sur un programme particulier, il est possible de connaˆıtre les d´ependances directes entre les instructions grˆace aux arcs construits mais aussi les d´ependances indirectes grˆace aux chemins form´es par des suites d’arcs dans le graphe (le graphe de la figure 2.2 page 25 en est un exemple). En ajoutant `a l’algorithme pr´ec´edent une condition dans la boucle principale permettant de parcourir le graphe construit lorsqu’on rencontre une instruction de sortie (affichage), il devient 23
  24. 24. possible de connaˆıtre les instructions utiles lors de l’ex´ecution d’un programme (d’apr`es la premi`ere d´efinition donn´ee ci-dessus) (cf. algorithme 3). Proc´edure Parcours du graphe (Noeud) 1 si le noeud est marqu´e inutile alors 2 Marquer ce noeud {repr´esentant une instruction} comme ´etant utile; pour chaque noeud op´erande de ce noeud faire 3 Appeler la proc´edure Parcours du graphe (Noeud op´erande); fin fin Algorithme 3: D´etection des instructions inutiles Entr´ee : Graphe de d´ependance de donn´ee dynamique. Sortie : Quantit´e d’instructions dynamiques inutiles. Localisation de ces instructions dans le code source. pour chaque noeud du graphe faire si le noeud repr´esente une instruction de sortie ou de branchement alors Appeler la proc´edure Parcours du graphe (Noeud); fin fin Numéro d'instruction dynamique Identificateur d'instruction statique Type de l'instruction Nombre d'opérandes Liste des instructions ayant écrit en dernier dans les opérandes Fig. 2.1 – La structure de donn´ee d’un noeud du graphe Un point int´eressant de cet algorithme, dont nous nous sommes rendu compte une fois l’impl´e-mentation op´erationnelle, est qu’il permet de d´etecter les acc`es fait en lecture `a une zone m´emoire non initialis´ee pr´ealablement. Par exemple, si un tableau de taille n est d´eclar´e et initialis´e, le fait de tenter d’acc´eder `a l’adresse de la zone m´emoire n+1, qui n’a donc pas ´etait initialis´ee, provoque une incoh´erence dans l’algorithme puisqu’il est impossible de trouver la derni`ere instruction ayant ´ecrit dans cette zone m´emoire (ligne 5 dans l’algorithme 1 page pr´ec´edente). De cette fa¸con, il est possible de trouver, lors de l’ex´ecution, une erreur d’acc`es `a la m´emoire. 24
  25. 25. 39 40 38 36 37 35 34 3 14 33 0 13 25 24 32 31 30 29 28 26 27 23 17 16 22 21 20 19 18 15 9 12 11 8 10 7 2 6 5 4 1 Dans ce graphe, les noeuds sont ´etiquet´es par les num´eros dynamiques des instructions (ordre d’ex´ecution). Les noeuds en gris sont des instructions inutiles alors que les noeuds en noir sont des instructions utiles. Les instructions dynamiques 4, 12 et 20 sont issues d’une seule et mˆeme instruction statique de rangement en m´emoire dont seulement une instance est utile : l’instruction dynamique num´ero 12. Fig. 2.2 – Exemple de graphe g´en´er´e par l’algorithme 1 & 3 25
  26. 26. 2.2.2 L’optimisation Le gros probl`eme de cette approche est que le graphe devient tr`es rapidement ´enorme, mˆeme avec des programmes de petite taille. En effet, ´etant donn´e qu’il faut conserver des informations concernant chaque instruction dynamique jusqu’`a la fin de l’ex´ecution, seule une ex´ecution ayant un nombre r´eduit d’instructions dynamiques peut ˆetre envisag´ee (aux alentours de 300 000 dans notre impl´ementation). Nous avons donc tent´e de r´eduire le graphe au maximum en ´eliminant au cours de l’ex´ecution les informations qui ne nous ´etaient plus n´ecessaires. Ce qui est fait. . . Dans un premier temps, pour r´eduire ce graphe et donc augmenter la taille des programmes testables, nous avons d´ecid´e de supprimer `a la vol´ee les informations concernant les instructions « certifi´ees » utiles. Ces informations ´etant le noeud repr´esentant cette instruction et les arcs sor-tant de celle-ci. Ces informations ne sont plus d’aucune utilit´e une fois que le parcours de l’arbre dont cette instruction est la racine est effectu´e. Il est alors possible de les supprimer sans perdre d’information utile `a notre calcul de quantit´e de travail inutile. Cette m´ethode permet, `a mesure que le programme se d´eroule et envoi des informations en sortie, (affichage. . .) de r´eduire le graphe. Il est alors d’autant plus r´eduit que la quantit´e de travail utile est importante (sur nos tests, le gain r´eel en occupation m´emoire est d’un facteur trois `a quatre ce qui permet de tester des programmes d´epassant le million d’instructions dynamiques sans avoir un temps d’ex´ecution r´edhibitoire). La courbe de l’occupation m´emoire de l’algorithme au cours du temps devient alors identique (`a quelques d´etails d’impl´ementation pr`es) `a la courbe repr´esentant la quantit´e de travail inutile cumul´e (figure 2.9 page 40). Quelques petites optimisations ont aussi ´etaient apport´ees au programme concernant le temps d’ex´ecution. Bien que ce facteur ne soit pas le point crucial de notre algorithme, il semblait int´eressant de s’y pencher pour ´eviter d’avoir des dur´ees de tests trop importantes. Nous avons par exemple, `a la ligne 1 de la proc´edure Parcours du graphe page 24, supprim´e le parcours d’une branche lorsque cette derni`ere poss`ede comme racine une instruction utile. En effet, si tel est le cas, cela signifie que cette branche a d´ej`a ´et´e enti`erement explor´ee et qu’il est inutile de la parcourir `a nouveau. Ce qu’il reste `a faire. . . Dans un second temps, il est int´eressant de voir que si une instruction ´ecrit dans une ou plu-sieurs ressources, puis que ces ressources sont de nouveau ´ecrites sans ˆetre lues entre temps, les informations concernant cette instruction inutile ne nous serviront jamais puisque cette instruction ne sera jamais rendue utile (notion de « valeur morte »). De cette fa¸con, il est possible, ici encore, 26
  27. 27. de r´eduire notre graphe en supprimant les noeuds repr´esentant ce type d’instructions ainsi que leurs arcs sortants. Une mani`ere simple d’impl´ementer un tel m´ecanisme serait de consid´erer que chaque instruction inutile est un objet et que les ressources (registres et zones m´emoire) sont des moyens d’acc´eder `a ces objets. Si une instruction est accessible depuis au moins une ressource, alors il n’est pas possible de supprimer les informations concernant cette instruction. En revanche, si aucune ressource ne « r´ef´erence » l’objet, alors cet objet est inaccessible depuis les ressources et le restera jusqu’`a la fin de l’ex´ecution du programme. Cet objet peut donc ˆetre supprim´e (noeud ainsi que ses arcs sortants). Cette m´ethode s’apparente `a un syst`eme de ramasse-miettes comme il est souvent mis en place dans un environnement d’ex´ecution pour lib´erer des zones m´emoires n’´etant plus r´ef´erenc´ees par aucun pointeur. 2.2.3 Le r´esultat La figure 2.3 page suivante est un exemple simple permettant de comprendre comment est construit le graphe de d´ependance de donn´ee `a partir du code assembleur du programme dont on veut ´evaluer la quantit´e de travail inutile. Dans la figure 2.3 page suivante, les noeuds sources sont les instruction utiles par hypoth`ese (en caract`ere gras). Ces instructions sont soit des instructions de sortie (print %valeur dans l’exemple) soit des instructions de branchement (bne boucle dans l’exemple). Une fois ces instructions jug´ees comme ´etant utile au programme, nous pouvons appliquer la d´efinition r´ecursive permettant de trouver toutes les instructions ayant servi `a produire les valeurs utiles `a ces instructions. Ainsi, dans notre exemple, l’instruction dynamique num´ero 18 (print %valeur) poss`ede comme entr´ee le registre %valeur. Il est donc n´ecessaire de trouver la derni`ere instruction ayant ´ecrit dans ce registre. Cette instruction est l’instruction dynamique num´ero 17 (load [@tab+2],%valeur). Ainsi de suite r´ecursivement, l’instruction dynamique num´ero 17 poss`ede comme entr´ee la seconde case du tableau rang´e `a l’adresse m´emoire @tab dont la derni`ere ´ecriture a ´et´e faite par l’instruction dynamique num´ero 8 et ainsi de suite jusqu’`a n’arriver qu’`a des instructions n’ayant aucune entr´ee (copie d’une constante dans un registre (mov 1,%indice dans l’exemple), instruction d’entr´ee au clavier par l’utilisateur. . .). De cette mani`ere, en parcourant le graphe, il est possible d’identifier le travail utile. Les ins-tructions n’´etant accessible depuis aucune des sources (instructions de sorties ou de branchement) sont identifi´ees comme ´etant du travail inutile (instructions dynamiques 2, 3, 12 et 13 dans notre exemple). Grˆace `a cet exemple, nous avons mis en ´evidence un cas simple comportant peu de travail inutile. En revanche, il est facile de prendre conscience de l’importance que peut atteindre ce travail inutile d`es lors que le traitement `a l’int´erieur d’une boucle du type de celle pr´esent´ee dans l’exemple devient important. En effet, dans notre exemple, seule la multiplication par 10 et le rangement en m´emoire sont inutiles mais si la valeur `a ranger dans le tableau avait ´et´e un calcul effectu´e par une fonction comportant 10 000 instructions, les chiffres auraient ´et´e diff´erents. De mˆeme, si la taille du tableau avait ´et´e de 10 000 cases dont seulement une aurait ´et´e utilis´ee, la quantit´e de travail inutile aurait ´et´e beaucoup plus importante. En revanche si, dans notre exemple, les trois cases du tableau avaient 27
  28. 28. 1: mov 1,%indice boucle: 2: mul %indice,10,%valeur 3: store %valeur,[%indice+@tab-1] 4: add %indice,1,%indice 5: cmp 4,%indice 6: bne boucle 7: load [@tab+2],%valeur 8: print %valeur Code en assembleur RISC (code statique) Dépendances de données permettant d’identifier le travail utile (arcs du graphe parcourus par l’algorithme). Dépendance de donnée n’étant pas parcourues par l'algorithme. Trace d’exécution des instructions (dynamique) 1ère itération de la boucle 2ème itération de la boucle 3ème itération de la boucle Numéro Dynamique Numéro Statique 123456789 10 11 12 13 14 15 16 17 18 123456234562345678 n Numéro d’instruction statique utile par essence (instructions de sorties ou de branchement). n Numéro d’instruction statique utile après parcours du graphe de dépendance (définition récursive). n Numéro d’instruction dynamique (indique l’ordre d’éxécution des instructions dans le temps) n Numéro d’instruction statique jugés comme étant inutile d’après la définition (instruction n’appartenant pas au graphe de dépendance de données). Compilation Pour i de 1 à 3 faire t[i] := i*10; Finpour Ecrire (t[2]); Code source original Fig. 2.3 – Du code source en langage de haut niveau au graphe de d´ependance de donn´ee dynamique ´et´e affich´ees (instruction print), la quantit´e de travail inutile aurait ´et´e nulle. Un exemple plus complet montrant dans le d´etail comment l’algorithme a ´et´e impl´ement´e est en annexe (figure 3.2 page 59). 28
  29. 29. 2.3 L’environnement de travail : Les choix de mise en oeuvre 2.3.1 Les Outils Pour la mise au point de notre programme d’´evaluation de la quantit´e de travail inutile, plusieurs outils ont ´et´e mis `a contribution : - Salto : Salto est une biblioth`eque de fonctions permettant d’analyser du code source en assembleur pour en extraire les informations s´emantiques sous une forme exploitable en C++. Ces informations peuvent ˆetre de diff´erentes natures : Il est possible de connaˆıtre le d´ecoupage en blocs de base du code source, les ressources utilis´ees par une instruction pr´ecise (dans notre cas, ce qui nous int´eresse sont les acc`es `a la m´emoire et aux diff´erents registres). De plus, une des fonctionnalit´e indispensable `a notre r´ealisation disponible dans Salto est la possibilit´e d’instrumenter le code source (figure 2.4 page suivante). - Le compilateur GCC pour processeur Sparc : Compilateur C/C++ gratuit sous licence GNU. - Le compilateur CC pour processeur Sparc : Compilateur C propri´etaire de Sun disponible seulement pour la plateforme Sparc. - Les expressions r´eguli`eres en C. 2.3.2 L’instrumentation L’instrumentation c’est quoi ? L’instrumentation d’instruction est un m´ecanisme qui consiste `a ins´erer des instructions suppl´e-mentaires entre les instructions du code source d’un programme d´ej`a ´etabli afin « d’ausculter » ce dernier. Les informations qu’il est possible de r´ecup´erer par ce m´ecanisme sont de nature dynamique puisque les instructions rajout´ees par instrumentation sont ex´ecut´ees autant de fois que le sont les instructions appartenant au code source d’origine. Prenons comme exemple le cas d’une boucle dont le corps est ex´ecut´e n fois, alors le code rajout´e par instrumentation du code source d’origine dans le corps de cette boucle sera lui aussi ex´ecut´e n fois. Ceci permet de savoir de fa¸con pr´ecise quelles sont les instructions statiques qui ont ´et´e ex´ecut´ees par le processeur s´equentiellement. De plus, l’instrumentation permet de r´ecup´erer d’autres informations dynamiques comme les adresses des acc`es `a la m´emoire. Dans l’exemple de la figure 2.4 page suivante, le code source original est instrument´e afin de r´ecup´erer la valeur du r´esultat produit par l’instruction `a instrumenter et pour le traiter dans la fonction fct (tmp ´etant par exemple un registre de d´ebuggage dont le code source original ne fait jamais usage mais qui peut ˆetre utilis´e par la fonction fct pour effectuer son traitement). 29
  30. 30. mov r1,r2 add r2,3,r2 store r2,[a0] Code source mov r1,r2 mov r2,tmp call fct add r2,3,r2 mov r2,tmp call fct store r2,[a0] load [a0],tmp call fct Code source instrumenté instrumentation Fig. 2.4 – Instrumentation de code source en assembleur Note Dans l’exemple de la figure 2.4, les instructions sont instrument´ees apr`es leur ex´ecution, ce qui n’est pas le cas dans notre impl´ementation : l’instrumentation se trouve avant l’ex´ecution de l’instruction pour des raisons de suivi des branchements (pour pouvoir instrumenter correctement les branchements, il est n´ecessaire de placer le code d’instrumentation avant ceux-ci). L’instrumentation dans notre programme L’instrumentation, dans le cas g´en´eral, permet d’ins´erer des instructions dans un programme. A partir de ce concept simple, nous avons d´ecid´e d’utiliser l’instrumentation pour ins´erer des appels de fonctions (´ecrites en C et compil´ees par ailleurs). De fait, les appels de fonctions en assembleur ne faisant pas de sauvegarde des registres globaux (accessible de n’importe o`u dans le programme). Il nous a fallu ajouter `a cette instrumentation une sauvegarde de contexte avant l’appel `a cette fonction puis une restauration apr`es (figure 2.5 page suivante). Mais ce n’est pas tout : Chaque instruction assembleur ayant un nombre variable d’op´erandes et de r´esultats, il nous a fallu ajouter une instruction d’appel `a une fonction pour chaque op´erande et pour chaque r´esultat. De plus, chaque acc`es `a la m´emoire n´ecessitant la r´ecup´eration de l’adresse de cet acc`es, il nous a fallu r´ecup´erer des informations sur la valeur des registres utilis´es par l’instruction `a instrumenter afin de savoir quelle ´etait l’adresse de cet acc`es m´emoire. L’instrumentation d’une instruction dans notre programme d’´evaluation de la quantit´e de travail inutile peut-ˆetre r´esum´ee en sept phases : – Sauvegarde : Une phase de sauvegarde du contexte (registres globaux, d´ecalage de la fenˆetre de registres. . .). – D´ebut : Une phase de cr´eation de la structure de donn´ee repr´esentant une instruction (fi-gure 2.1 page 24). – Op´erandes : Une phase permettant de cr´eer les arcs vers les instructions ayant ´ecrit en dernier dans les ressources op´erandes de l’instruction. 30
  31. 31. inst1 inst2 inst3 Code source sauvegarde call fct restauration inst1 sauvegarde call fct restauration inst2 sauvegarde call fct restauration inst3 Code source instrumenté instrumentation Fig. 2.5 – Principe de l’instrumentation faite par le programme d’´evaluation de la quantit´e de travail inutile – Milieu : Une phase, utile seulement pour les instruction « save » et « restore », permettant de mettre `a jour le niveau de la fenˆetre de registres. – R´esultats : Une phase permettant de mettre `a jour l’´etat des ressources en fonction des r´esultats produits par l’instruction. – Fin : Une phase permettant d’´evaluer l’utilit´e de l’instruction courante. Si cette derni`ere est utile, on parcour son arbre de d´ependance de donn´ee. – Restauration : Une phase de restauration du contexte. 2.3.3 Le choix de la Plateforme Pour choisir notre plateforme de travail, nous avons pris en compte plusieurs param`etres. Nous avions le choix entre le jeu d’instruction x86 (CISC) et le jeu d’instruction Sparc (RISC). En premier lieu, l’outil mis `a notre disposition (Salto) semblait plus adapt´e `a un jeu d’instruction r´eduit. En effet, Salto ´etant bas´e sur la reconnaissance des instructions assembleur par des expressions r´eguli`eres, il est tr`es difficile de supporter un jeu d’instruction aussi vaste que le x86 d’Intel (CISC). De fait, le support d’un tel jeu d’instruction par Salto est apparu comme ´etant insuffisant. De plus, le travail `a effectuer ´etant, entre autre, d’identifier les acc`es `a la m´emoire, un jeu d’instruction r´eduit avec seulement une instruction pour le chargement et une pour le rangement en m´emoire nous est apparu plus simple `a manipuler. Cependant, le Sparc poss`ede quelques inconv´enients qui, nous le verrons plus loin, ne nous ont pas facilit´e la tˆache. Nous avons donc d´ecid´e de travailler avec un jeu d’instruction RISC pleinement support´e par Salto : le Sparc de Sun. 31
  32. 32. 2.3.4 SPARC : Le Meilleur des Mondes ? Le Sparc est une architecture RISC assez classique ce qui signifie que le nombre d’instructions est assez r´eduit, qu’elles ont toutes la mˆeme taille (quatre octets pour le Sparc) et que, chose relativement importante pour notre impl´ementation, les instructions d’acc`es `a la m´emoire sont au nombre de deux (load pour charger une valeur de la m´emoire vers un registre et store pour ranger une valeur d’un registre vers la m´emoire). Tout aurait ´et´e parfait si la description du Sparc s’´etait arrˆet´ee l`a. . . - Le delay slot est une des particularit´es de l’architecture Sparc. Il s’agit d’ex´ecuter l’instruc-tion qui suit imm´ediatement une instruction de branchement avant que cette instruction de branchement ne modifie le flot de contrˆole de l’ex´ecution du programme. Pour r´esumer, l’ins-truction qui se trouve dans le delay slot d’un branchement (elle se trouve juste apr`es dans le code source) se comporte comme si elle se trouvait avant le branchement pour ce qui est du contrˆole mais comme si elle ´etait apr`es pour ce qui est des donn´ees. - L’annul bit est une autre particularit´e « amusante » du Sparc qui est en relation directe avec le delay slot. Lorsqu’une instruction se trouve dans le delay slot d’un branchement dont l’annul bit est activ´e, (be,a : la virgule et le a indique que l’annul bit est activ´e) cette instruction ne s’ex´ecute que lorsque le branchement est pris. Dans le cas contraire, l’instruction se trouvant dans le delay slot n’est pas ex´ecut´ee (on dit qu’elle est annul´ee). - La fenˆetre de registres tournante est une fa¸con de pallier `a la lenteur des acc`es `a la pile lors du passage de param`etres `a une fonction. En effet, le Sparc se propose de r´esoudre le probl`eme du passage de param`etres `a une fonction de mani`ere originale au moyen de cette fenˆetre de registres tournante. Il s’agit de ranger les valeurs que l’on souhaite passer en param`etres `a la fonction qui va ˆetre appel´ee dans des registres sp´eciaux nomm´es registres de sortie (%o0 `a %o5) qui, une fois la fonction appel´ee, seront renomm´es en registres d’entr´ee (%i0 `a %i5) (cf. figure 2.6 page suivante). De cette fa¸con, la fonction appel´ee peut se servir de ces valeurs sans qu’elles n’aient ´et´e recopi´ees dans une quelconque pile (`a l’exception du cas o`u la taille de la fenˆetre de registres tournante n’est pas suffisante). Le delay slot Dans notre impl´ementation, nous avons du prendre en compte cette particularit´e de l’archi-tecture Sparc. En effet, afin d’instrumenter les instructions se trouvant dans le delay slot d’un branchement, nous avons du utiliser diff´erentes techniques consistant `a « remonter » notre instru-mentation de ce type d’instructions avant le branchement. Ceci `a conduit `a pas mal de probl`emes de coh´erence entre la repr´esentation des instructions cr´e´ees par l’instrumentation et les instructions r´eellement ex´ecut´ees. Mais le cas o`u le delay slot nous a pos´e le plus de probl`eme est celui o`u il nous a fallu ajouter des sauts afin d’´eviter l’ex´ecution de certaines instructions. En effet, dans ce cas, si l’instruction que l’on veut « sauter » se trouve dans le delay slot d’un branchement, il faut dupliquer cette instruction des branchement : mettre dans le code source une version normale avec l’instruction qui se trouvait dans son delay slot dans le code source d’origine et une seconde version du mˆeme branchement contenant un nop dans son delay slot. De cette fa¸con, selon que l’instruction se trouvant dans le 32
  33. 33. delay slot de ce branchement doit ˆetre ex´ecut´ee ou non, le flot de contrˆole est aiguill´ee vers l’une ou l’autre des deux versions de ce branchement. Il faut ensuite faire converger les deux versions en un mˆeme point repr´esentant la suite du programme. La fenˆetre de registres du Sparc La fenˆetre de registre tournante est une des particularit´e du processeur Sparc. Nous allons en expliquer rapidement le principe afin d’exposer la mani`ere dont nous avons trait´e cette particularit´e dans notre programme. Registres de sortie %o0 à %o7 Registres locaux %l0 à %l7 Registres d’entrée %i0 à %i7 Registres accessibles au niveau n+1 niveau n Registres de sortie %o0 à %o7 Registres locaux %l0 à %l7 Registres d’entrée %i0 à %i7 Désigne les mêmes registres physiques Registres de sortie %o0 à %o7 Registres locaux %l0 à %l7 Registres d’entrée %i0 à %i7 niveau n+2 Désigne les mêmes registres physiques L’instruction « save » permet de passer d’un niveau n `a un niveau n+1 (utilis´e g´en´eralement comme une sauvegarde de contexte avec en plus la possibilit´e de passer des param`etres d’un contexte `a l’autre au niveau de la zone de recouvrement de la fenˆetre n et n+1). L’instruction « restore » permet de passer d’un niveau n `a un niveau n-1 (utilis´e g´en´eralement comme une restauration de contexte avec en plus la possibilit´e de passer des r´esultats d’un contexte `a l’autre au niveau de la zone de recouvrement de la fenˆetre n et n-1). Fig. 2.6 – Le principe de la fenˆetre de registres du Sparc Lorsque le nombre de registres utilis´es d´epasse le nombre de registres physiques r´eellement pr´esents dans le processeur, un m´ecanisme invisible pour l’utilisateur utilise une pile en m´emoire pour sauvegarder la fenˆetre de registres la plus ancienne et r´eutiliser ainsi cette derni`ere comme une nouvelle fenˆetre vierge. De cette fa¸con, le nombre de registres virtuellement utilisables par l’utilisateur n’est limit´e que par la taille de la pile et non par la taille du fichier de registres dans le processeur. Cette fenˆetre est souvent repr´esent´ee, dans les diff´erentes documentations sur le Sparc, de fa¸con circulaire pour montrer que la plus ancienne fenˆetre et la plus r´ecente peuvent se recouvrir si le nombre de fenˆetres disponibles dans le fichier de registres est insuffisant pour l’ex´ecution d’un programme donn´e. 33
  34. 34. 2.3.5 S’affranchir de la num´erotation des registres faite par Salto Les registres du Sparc s’organisent en deux parties. D’une part, des registres dit globaux qui se comportent de mani`ere classique, et d’autre part une fenˆetre de registres coulissante comme d´ecrit sur la figure 2.6 page pr´ec´edente. Pour les registres globaux, nous avons utilis´e la num´erotation propos´ee par Salto qui convenait tout `a fait ´etant donn´e qu’un nom de registre d´esigne toujours le mˆeme registre physique. En revanche, pour la fenˆetre de registres coulissante, nous avons du mettre en place notre propre syst`eme d’identification de registres : En effet, Salto ´etant un outil qui travaille sur du code assembleur, il lui est impossible d’avoir acc`es `a des informations concernant l’ex´ecution du programme (les seules informations disponible au niveau de Salto sont les informations statiques sur le programme). De fait, les informations que nous donne Salto concernant les acc`es aux registres sont les noms de ces registres. Il lui est impossible de connaˆıtre le niveau de la fenˆetre de registre en un point donn´e du code et donc de d´esigner un registre physique de mani`ere unique. Afin d’identifier de mani`ere unique chacun des registres physiques, il a donc fallu s’affranchir du syst`eme de num´erotation des registres propos´e par Salto pour le remplacer par un calcul fait de mani`ere dynamique (au cours de l’ex´ecution du programme) permettant de savoir `a quel registre physique correspondait chaque acc`es `a un registre appartenant `a la fenˆetre de registre courante. Pour ce faire, nous avons instrument´e les instructions save qui d´ecalent la fenˆetre de registre vers le haut et les instructions restore qui d´ecalent la fenˆetre de registre vers le bas (cf. figure 2.6 page pr´ec´edente). De cette fa¸con, il est possible de tenir `a jour une variable globale indiquant le niveau actuel de la fenˆetre de registre. En utilisant ce niveau comme d´ecalage par rapport `a un point de r´ef´erence, (la premi`ere fenˆetre disponible lors du lancement du programme) il est possible de savoir dans quelle fenˆetre de registre seront fait les acc`es aux registres de la fenˆetre courante indiqu´es par Salto. Grˆace `a cette m´ethode, il est possible d’identifier de mani`ere unique chacun des registres d’une fenˆetre pr´ecise mˆeme si celle-ci peut avoir ´et´e sauvegard´ee dans la pile par manque de place dans le fichier de registres. Les acc`es `a ces registres restent ce qu’ils sont puisque cette op´eration est transparente pour l’utilisateur du processeur (en l’occurrence notre programme). 2.3.6 Les expressions r´eguli`eres L’utilisation des expressions r´eguli`eres est particuli`erement utile pour analyser une chaˆıne de caract`eres ayant un motif fixe et une partie variable. C’est justement le cas d’une instruction assembleur dont la partie fixe est le mn´emonique de cette instruction et dont les parties variables sont les arguments. De cette fa¸con, nous avons utilis´e les expressions r´eguli`eres pour « d´ecouper » les instructions load et store en plusieurs parties : le mn´emonique d’une part (permettant de connaˆıtre la taille de l’acc`es `a la m´emoire : octet, mot, double mot ou quadruple mot) et les arguments d’autre part. Une 34
  35. 35. fois chaque argument r´ecup´er´e, il est possible d’acc´eder aux valeurs contenues dans les registres et aux ´eventuelles constantes. Il est donc possible de connaˆıtre de fa¸con exacte `a quelle adresse m´emoire va acc´eder l’instruction et `a combien d’octets elle va acc´eder. De plus, les expressions r´eguli`eres nous ont ´et´e utiles pour r´ecup´erer les ´etiquettes des appels de fonctions afin de savoir si ces fonctions ´etaient d´efinies en local (dans le code source du pro-gramme ´etudi´e) ou si elles appartenaient `a une biblioth`eque externe au programme (stdio en C par exemple). 2.3.7 La gestion des fonctions Une fois notre d´efinition impl´ement´ee et test´ee, il reste encore de nombreux probl`emes `a r´esoudre pour pouvoir confronter cet algorithme au « monde r´eel » (`a des programmes classiques tel que gzip). En effet, ce mod`ele th´eorique serait parfait si l’ensemble du code source assembleur d’une application ´etait visible par le programme d’´evaluation. Or les programmes classiques utilisent des fonctions d´efinies dans des biblioth`eques dont on n’a pas le code source (Un programme en C travaillant sur des fichiers utilisera par exemple stdio pour lire et ´ecrire dans un fichier). Afin de r´egler cette difficult´e, nous avons du utiliser l’options de compilation -SO de cc permettant de savoir quels sont les registres du Sparc et les adresses m´emoires utilis´ees dans la pile comme param`etres lors de l’appel `a une fonction. Ainsi, il nous a ´et´e possible de rajouter « artificiellement » des arcs de d´ependance entre l’appel `a une fonction d´efinie dans une biblioth`eque externe (call printf par exemple) et les param`etres pass´es `a cette fonction. Pour cette raison, cc pour Sparc est apparu comme ´etant id´eal dans notre protocole de test. Afin de r´esoudre ce probl`eme, les fonctions appel´ees par le programme ´etudi´e ont ´et´e class´ees en trois cat´egories, chacune correspondant `a un traitement particulier `a faire pour les prendre en compte correctement : Les fonctions « internes » Les fonctions internes sont les fonctions dont le code source est disponible (peut-ˆetre utilis´e par notre programme d’´evaluation). Les appels `a ce type de fonctions peuvent donc ˆetre trait´es comme de simple branchements inconditionnels puisque les arcs du graphe de d´ependance peuvent tr`es bien relier une instruction appartenant `a cette fonction `a une instruction appartenant au programme appelant. Le graphe de d´ependance de donn´ee n’est donc pas interrompu par un tel appel de fonction. Les fonctions « externes » utilisant des pointeurs Les fonctions externes sont les fonctions dont le code source n’est pas disponible (code source d’une biblioth`eque d’entr´ee/sortie par exemple). Dans ces cas l`a, il est n´ecessaire de consid´erer que les param`etres pass´es `a la fonction sont lus par l’instruction d’appel de la fonction et que le r´esultat 35
  36. 36. est ´ecrit par cette instruction. Cependant, dans le cas g´en´eral, il est n´ecessaire de d´etourner l’appel `a une telle fonction pour faire ajouter « artificiellement » des arcs de d´ependance de donn´ee pour traiter les donn´ees qui vont ˆetre lues et ´ecrites `a l’int´erieur de cette fonction. En effet, notre programme est incapable de connaˆıtre le type des param`etres d’une fonction (si il s’agit d’entiers, de pointeurs. . .) et donc, de savoir si une fonction d´efinie dans une biblioth`eque dont on n’a pas le code source acc`ede ou non `a une zone m´emoire dont l’adresse est un de ses param`etres. De plus, mˆeme en sachant qu’un param`etre est un pointeur, rien ne dit si la fonction dont on n’a pas le code source va y acc´eder en lecture, en ´ecriture ou pire encore, `a combien d’´el´ements elle va acc´eder ! (Il ne faut pas oublier qu’en C les tableaux sont implicites et que, par cons´equent, on ne connaˆıt pas leur taille simplement en connaissant l’adresse de leur premier ´el´ement). Afin de r´egler cette difficult´e, nous avons choisi de d´etourner les appels `a ces fonctions (au nombre de trente cinq dans gzip) afin de leur faire ex´ecuter du code permettant de simuler leur comportement en mati`ere d’acc`es `a la m´emoire. Ces fonctions sont, le plus souvent des fonctions d’entr´ees/sorties (ex : read, write, fflush. . .), des fonctions de lecture/´ecriture dans des chaˆınes de caract`eres (ex : strcat, strcpy, strcmp. . .) ou des fonctions de lecture/´ecriture de zones m´emoire en g´en´eral (ex : memset, memcpy, memcmp. . .). Exemple simple : char *my_strcpy(char *c1, const char *c2) { /* On simule un acc`es en lecture `a une zone m´emoire d´ebutant `a l’adresse contenue dans le pointeur c2 et de longueur strlen(c2)+1 (taille de la cha^ıne de caract`ere `a lire avec son terminateur) */ instrumentationEntreeMemoire((int)c2, strlen(c2)+1); /* On simule un acc`es en ´ecriture `a une zone m´emoire d´ebutant `a l’adresse contenue dans le pointeur c1 et de longueur strlen(c2)+1 (taille de la cha^ıne de caract`ere `a copier avec son terminateur) */ instrumentationSortieMemoire((int)c1, strlen(c2)+1); /* On retourne la valeur retourn´ee par la vraie fonction strcpy */ return strcpy(c1, c2); } Les fonctions « externes » n’utilisant pas de pointeurs Les fonctions externes n’utilisant pas de pointeurs sont des fonctions dites externes d’apr`es la d´efinition ci-dessus `a la diff´erence qu’elles ne font pas d’acc`es `a la m´emoire `a partir de pointeur. Elles peuvent donc ˆetre trait´ees simplement en consid´erant que les param`etres pass´es `a la fonction sont lus et que le r´esultat est ´ecrit. 36
  37. 37. 2.4 R´esultats & Analyse 2.4.1 Les chiffres. . . Une fois l’´evaluation de la quantit´e de travail inutile effectu´ee de cette mani`ere, nous obtenons les chiffres suivants (cf. figure 2.7). gzipa gzipb gzipc O0d 11.03 % 9.53 % 11.03 % O1 12.05 % 10.73 % 16.44 % O2 11.40 % 10.28 % 16.17 % O3 12.77 % 12.10 % 21.83 % O4 12.66 % 12.55 % 23.35 % O5 12.66 % 12.55 % 23.35 % gunzipe gunzipf gunzipg O0 1.09 % 1.14 % 0.21 % O1 2.94 % 3.10 % 0.29 % O2 3.51 % 4.28 % 0.41 % O3 10.01 % 10.51 % 0.47 % O4 9.83 % 10.26 % 0.52 % O5 9.83 % 10.26 % 0.52 % aCompression d’un fichier texte (RTF) de 2127 octets avec gzip (fort taux de compression (facteur : 1.9)) bCompression d’un fichier image (GIF) de 1704 octets avec gzip (faible taux de compression (facteur : 1.4)) cRe-compression d’un fichier compress´e par gzip de 1506 octets avec gzip (taux de compression nul (facteur : 0.98)) dSeuls ces r´esultats ont ´et´e valid´es avec le programme de v´erification pour des raisons d’impl´ementation eD´ecompression du fichier texte de 2127 octets fD´ecompression du fichier image de 1704 octets gD´ecompression du fichier compress´e deux fois par gzip de 1506 octets Conditions de test : gzip version 1.2.4 recompil´e avec cc de Sun pour le processeur Sparc version v8plus. Ces chiffres s’entendent en ne comptant pas les ´eventuelles instructions nop qui se trouvent dans le delay slot de certaines instructions de branchement. Fig. 2.7 – Quantit´e d’instructions assembleurs inutiles lors de l’ex´ecution de gzip dans diff´erentes conditions 2.4.2 Le doute. . . Introduction Ces chiffres n’´etant qu’une ´evaluation, rien ne permet de dire avec certitude que ce travail est effectivement inutile. D’autant plus que l’impl´ementation laisse souvent apparaˆıtre des failles que l’on n’imagine pas lorsqu’on raisonne de fa¸con abstraite sur les d´ependances de donn´ees (la gestion des fonctions dont on n’a pas le code source en est un exemple). Afin de valider notre programme et d’avoir la certitude que le travail inutile ´evalu´e en ´etait bien, nous avons mis au point un second programme r´e-ex´ecutant exactement le mˆeme programme de test (gzip dans notre cas) sur le mˆeme jeu de donn´ees (avec le mˆeme fichier en entr´ee) en n’ex´ecutant pas les instructions qui avaient ´et´e jug´ees comme ´etant inutiles lors de la premi`ere ex´ecution. Ainsi, si la seconde ex´ecution donne rigoureusement le mˆeme r´esultat que la premi`ere (le mˆeme fichier compress´e dans le cas de gzip), nous pouvons dire que les deux ex´ecutions sont ´equivalentes et que, par cons´equent, les instructions qui n’ont pas ´et´e ex´ecut´ees lors de la seconde ex´ecution n’´etaient effectivement pas utiles. 37
  38. 38. somewhere : sub r3,r4,r5 ... mov r1,r2 add r2,3,r2 cmp 0,r2 be somewhere store r2,(a0) ... Code source mov r1,r2 add r2,3,r2 cmp 0,r2 be somewhere sub r3,r4,r5 ... Trace dynamique de la première exécution (détection du travail inutile) mov r1,r2 add r2,3,r2 cmp 0,r2 be somewhere store r2,(a0) ... Trace dynamique de la deuxième exécution (non exécution du travail inutile) L'instruction add est jugée inutile : elle ne sera donc pas exécutée lors de la seconde exécution Fig. 2.8 – Mise en ´evidence d’un probl`eme d’impl´ementation par divergence du flot de contrˆole Note Reproduire une ex´ecution `a l’identique ne fonctionne que sur un programme d´eterministe : deux ex´ecutions successives sur un mˆeme jeu de donn´ee doivent s’ex´ecuter rigoureusement de la mˆeme mani`ere. De fait, il serait complexe de traiter des programmes utilisant des fonctions de tirages al´eatoires ou conservant des informations d’une ex´ecution sur l’autre (cache dans un fichier par exemple). Ce n’est pas le cas de gzip ce qui nous a permis de mener nos tests de fa¸con correcte sur ce programme. L’int´erˆet Un aspect tr`es int´eressant de l’utilisation de ce programme de v´erification est qu’il a permis d’affiner le programme d’´evaluation de la quantit´e de travail inutile. En effet, en observant les divergences entre la premi`ere et la seconde ex´ecution (figure 2.8), il a ´et´e possible de trouver les points faibles du programme d’´evaluation de la quantit´e de travail inutile et de les consolider afin de rendre les deux ex´ecutions ´equivalentes. Par exemple, lorsque la seconde ex´ecution du programme de test divergeait de la premi`ere (un branchement pris alors qu’il n’aurait pas du par exemple), cela signifiait qu’une instruction utile au flot de contrˆole avait ´etait jug´ee comme ´etant inutile `a tort. De cette mani`ere, il a ´et´e possible de trouver les incorrections du programme d’´evaluation du travail inutile et surtout de mettre en lumi`ere les lacunes d’une impl´ementation trop « na¨ıve » par rapport aux appels de fonctions d´efinies dans des biblioth`eques dont le code source n’est pas disponible. Dans l’exemple de la figure 2.8, le branchement be (branch if equal) est pris lors de la premi`ere ex´ecution alors qu’il n’est pas pris lors de la seconde, ce qui entraˆıne une incoh´erence entre les deux 38
  39. 39. ex´ecutions qui ne sont alors plus ´equivalentes. Ceci se produit en raison d’un mauvais jugement port´e sur l’instruction add. En effet, celle-ci est utile au bon d´eroulement du programme alors qu’elle a ´et´e jug´ee comme ne l’´etant pas par le programme de d´etection du travail inutile. Grˆace `a ce syst`eme, il est facile de corriger les incorrections et impr´ecisions que comporte le programme de d´etection du travail inutile. Par processus incr´emental, il est alors possible de corriger ces erreurs une `a une jusqu’`a l’obtention d’un programme qui ne juge inutile que du travail r´eellement inutile (Ce qui ne prouve pas pour autant qu’il d´etecte tout le travail inutile que peut comporter un programme). La M´ethode Pour pouvoir mettre au point ce deuxi`eme programme, il faut tout d’abord que le programme d’´evaluation de la quantit´e de travail inutile laisse une trace des instructions inutiles dans un fichier qui sera utilis´e par le programme de v´erification. Pour la mise au point de ce programme de v´erification, Salto a, l`a encore, ´et´e sollicit´e afin d’instrumenter chaque instruction. Lorsque cette instruction est jug´ee inutile (d’apr`es la trace de la premi`ere ex´ecution) alors cette instruction est saut´ee au moyen d’un jump d’une valeur constante puisque, dans les processeurs Sparc, toutes les instructions ont une taille de quatre octets (« merci » les jeux d’instructions RISC). De cette fa¸con, il est assez facile de « sauter » une instruction lorsqu’elle apparaˆıt dans la trace des instructions inutiles. Conclusion Ce programme `a permis, sur des exemples simples, de v´erifier que la quantit´e de travail inutile ´evalu´ee ´etait bien du travail inutile quelque soit le niveau d’optimisation utilis´e et les cas de figure rencontr´es. Cependant, par manque de temps, nous n’avons r´eussi `a le faire fonctionner que sur gzip compil´e avec un niveau d’optimisation de 0. N´eanmoins, cette v´erification nous `a permis d’accroˆıtre la confiance en nos r´esultats (figures 2.7 page 37 et 2.9 page suivante). 2.4.3 La r´epartition du travail inutile Une fois les informations sur le travail inutile lors de l’ex´ecution d’un programme r´ecup´er´ees, il est n´ecessaire de les organiser afin de pouvoir analyser d’ou provient ce travail inutile. Dans un premier temps, nous avons essay´e de voir `a quelles instructions statiques correspondaient nos instructions dynamiques inutiles. Nous avons trouv´e, sans surprise ´etant donn´e les r´esultats de l’article [1], que seul un petit nombre d’instructions statiques ´etaient concern´ees. Ce qui signifie que la plupart des instructions dynamiques inutiles sont concentr´ees sur un nombre d’instructions statiques r´eduit (de l’ordre de 12.4 % des instructions statiques totales g´en`erent au moins une instance dynamique inutile). Une fois ces instructions statiques en assembleur identifi´ees, nous avons cherch´e `a « remonter », lorsque c’´etait possible, au code source en C correspondant afin de mieux comprendre la raison pour laquelle ce travail est jug´e comme ´etant inutile par notre d´efinition. 39
  40. 40. 108876.0 90730.0 72584.0 54438.0 36292.0 18146.0 0.0 Compression d’un fichier RTF de 2127 octets Algorithme GZIP compile avec cc et un niveau d’optimisation de 0 0.0 268685.0 537370.0 806055.0 1074740.0 (a) Sans optimisations de compilation 53388.0 44490.0 35592.0 26694.0 17796.0 8898.0 0.0 Compression d’un fichier RTF de 2127 octets Algorithme GZIP compile avec cc et un niveau d’optimisation de 5 0.0 105695.0 211390.0 317085.0 422780.0 (b) Avec optimisations de compilation Abscisse : Num´ero d’instruction dynamiques : repr´esente le temps ´ecoul´e en nombre d’instructions Ordonn´ee : Quantit´e d’instructions inutiles (cumul´ees) Fig. 2.9 – ´Evolution de la quantit´e de travail inutile en fonction du temps Travail inutile algorithmique Introduction En observant les courbes de la figure 2.9 on s’aper¸coit que l’algorithme de gzip, dans nos conditions de test, se d´ecompose en plusieurs phases g´en´erant chacune des quantit´es de travail inutile diff´erentes. En premier lieu, il est int´eressant de noter que la phase d’initialisation comporte une grande proportion de travail inutile (presque 50 % avec un niveau d’optimisation de 0 et plus de 50 % avec un niveau de 5). Ensuite, vient une courte phase durant laquelle aucun travail inutile n’est pr´esent (quelque soit le niveau d’optimisation). Vient ensuite un ensemble de phases que nous appellerons le coeur de l’algorithme durant lequel on observe une quantit´e de travail inutile moyen non n´egligeable avec un niveau d’optimisation de 0 (de l’ordre de 6,9 %) et plus important encore avec un niveau d’optimisation de 5 (de l’ordre de 9,9 %). La phase d’initialisation Dans certains cas, le travail inutile semble ˆetre d’origine algorith-mique. En effet, en observant le code source en C de gzip, il apparaˆıt parfois du travail inutile qu’il serait simple d’´eviter en modifiant une petite partie du code. De fait, nous pouvons dire que ce travail inutile est inh´erent `a la fa¸con dont l’algorithme de gzip est impl´ement´e. De plus, ´etant donn´e une tr`es forte proportion de travail inutile durant la phase d’initialisation des structures de donn´ees utiles `a l’algorithme de gzip (aux alentours de 50%), il semble raisonnable de penser qu’une tr`es grande partie de ces structures de donn´ees sont initialis´ees puis jamais utilis´ees ou r´eutilis´ees pour ˆetre ´ecrites (ce qui g´en`ere des valeurs mortes). Ce type de travail inutile semble 40
  41. 41. ˆetre r´eellement inh´erent `a l’algorithme et non du `a une mauvaise impl´ementation de celui-ci. Exemple simple : Dans la fonction local void gen codes (tree, max code), on observe que le code source suivant est inutile la plupart du temps (lors de l’ex´ecution sur un fichier de test) : for (bits = 1; bits <= MAX_BITS; bits++) { next_code[bits] = code = (code + bl_count[bits-1]) << 1; } L’affectation dans le tableau next code est inutile 52 fois sur 60 dans l’exemple test´e (le nombre d’it´erations de la boucle est MAX BITS et est ´egal `a 60). Le fait que cette affectation soit inutile un certain nombre de fois engendre qu’une partie des calculs fait dans la boucle devient inutile. Ce qui nous donne, pour l’ensemble de la boucle, un nombre de 824 instructions inutiles pour 1440 au total (soit une proportion de 57 %). Etant donn´e ces r´esultats, il est int´eressant de se pencher sur le cas de l’initialisation des structures de donn´ees en g´en´eral. En effet, le premier r´eflexe d’un programmeur, lorsqu’il d´eclare une structure de donn´ee (tableau, liste. . .) est de l’initialiser pour ´eviter, par la suite, d’y faire un acc`es en lecture sans y avoir pr´ealablement rang´e une valeur. Or ce r´eflexe de programmation est probablement ce que nous observons ici ´etant donn´e que les structures de donn´ees de gzip n’´echappent apparemment pas `a cette r`egle. Le coeur de l’algorithme Au coeur de l’algorithme, nous observons diff´erents cas d’instructions dynamiques inutiles. Parfois, nous observons que le travail inutile est du `a l’initialisation de va-riables locales dont le contenu est, la plupart du temps, r´e-´ecrit avant d’ˆetre lu. Parfois, il s’agit de param`etres pass´es `a une fonction et qui ne servent que dans certaines conditions. Et enfin, un cas assez fr´equent ´egalement est celui des variables globales qui sont maintenues `a jour de fa¸con inutile. En effet, si une telle variable refl`ete une valeur lors du dernier passage dans une certaine fonction, il est possible que cette fonction soit appel´ee plusieurs fois sans que cette valeur n’ai ´et´e lue entre temps. Exemples : L’affectation prev match = match start ; peut-ˆetre inutile car la seule utilisation de la variable prev match en lecture est le cas suivant : if (prev_length >= MIN_MATCH && match_length <= prev_length) { check_match(strstart-1, prev_match, prev_length); flush = ct_tally(strstart-1-prev_match, prev_length - MIN_MATCH); ... } 41
  42. 42. Ce qui signifie que lorsque la condition ci-dessus sera fausse, l’affectation de la variable prev match sera inutile (c’est le cas 78 fois 82 dans notre test). En supposant que le calcul de la valeur de la variable match start puisse ˆetre coˆuteux, et que cette variable ne soit pas r´e-utilis´ee en lecture entre temps, on prend conscience de la port´ee que peut avoir le travail inutile. Note Dans l’exemple pr´ec´edent, il est int´eressant de noter que le d´eplacement de l’instruction d’affec-tation prev match = match start ; dans le corps de la conditionnelle aurait suffit `a ´eliminer ce travail inutile (dans la mesure o`u on ne fait pas d’´ecriture dans match start entre temps). En effet, la variable prev match n’´etant utilis´ee que dans ce bloc, il est inutile de faire cette affectation si la condition n’est pas vraie. Une macro un peu particuli`ere a ´egalement retenu notre attention. Elle se trouve au coeur de l’algorithme de compression, dans la fonction deflate(). Il s’agit de la macro INSERT STRING qui ins`ere une chaˆıne de caract`ere dans la liste des chaˆınes de caract`eres qu’utilise gzip pour trouver les chaˆınes les plus fr´equemment pr´esentes dans le fichier `a compresser. Voici le code de cette macro apr`es passage du pr´e-processeur : ((ins_h = (((ins_h)<<((15+3-1)/3)) ^ ( window[(strstart) + 3-1])) & ((unsigned)(1<<15)-1)), prev[(strstart) & (0x8000-1)] = hash_head = (prev+0x8000)[ins_h], (prev+0x8000)[ins_h] = (strstart)); Pour des raisons de lisibilit´e, nous avons r´e-´ecrit ce code : ins_h = ( ins_h<<5 ^ window[strstart+2] & (unsigned)(1<<15)-1 ); hash_head = (prev+0x8000)[ins_h]; prev[strstart & (0x8000-1)] = hash_head; (prev+0x8000)[ins_h] = strstart; Dans cette macro, qui se trouve au coeur de l’algorithme de compression, les deux derni`eres instructions (remplissage du tableau prev) se trouvent ˆetre tr`es souvent inutile (77 fois sur 82 dans notre exemple). Ceci tend `a montrer que l’algorithme de compression utilis´e par gzip contient, par nature, du travail inutile. Travail inutile introduit par le compilateur. . . . . . lors des phases d’optimisation de compilation On observe ´egalement que la version compil´ee avec un niveau d’optimisation de 0 pr´esente une quantit´e globale de travail inutile moins important (proportionnellement) `a la version compil´ee avec un niveau d’optimisation de 5. De plus, 42
  43. 43. l’´ecart entre les deux versions s’accentue dans le coeur de l’algorithme. En effet, durant la phase d’initialisation, les deux versions se comportent `a peu pr`es de la mˆeme fa¸con (aux alentours de 50 % de travail inutile) alors que dans le coeur de l’ex´ecution, la version non optimis´ee comporte en moyenne 6.9 % de travail inutile `a comparer aux 9.9 % observ´e dans le cas de la version optimis´ee. Ce ph´enom`ene avait d´ej`a ´et´e constat´e dans l’article [1] mais uniquement au sujet des valeurs mortes. . . . du au jeu d’instruction du processeur Cette ´etude n’est absolument pas exhaustive sur les diverses causes que peut avoir le travail inutile. Cependant, mˆeme si cet aspect n’a pu ˆetre explor´e pour des raisons de temps, il parait raisonnable de penser qu’une partie du travail inutile pourrait avoir ´et´e introduit en raison des contraintes impos´ees par le jeu d’instructions utilis´e. En effet, dans un jeu d’instruction RISC (comme le Sparc) une instruction de haut niveau (en langage C par exemple) peut ˆetre convertie par le compilateur en une suite tr`es importante d’instructions comme en une seule. Ceci d´epend de l’´eloignement de cette instruction en langage C par rapport aux instructions disponibles dans le jeu d’instruction assembleur utilis´e. A contrario, un jeu d’instruction CISC (comme le x86) aura des instructions assembleur plus proche des instructions en langage de haut niveau. De cette fa¸con, les proportions d’instructions assembleur inutiles peuvent ne pas ˆetre identiques aux proportions d’instructions inutiles de haut niveau (en langage C par exemple). De plus, certaines optimisations de compilation effectuant un r´e-ordonnancement des instructions assembleur, il est parfois difficile de savoir quel ensemble d’instructions assembleur repr´esente quelle instruction de haut niveau. Conclusion En conclusion, nous pouvons dire que les proportions de travail inutile trouv´ees se rattachent majoritairement au travail inutile pr´esent dans l’algorithme en langage de haut niveau. De fait, une piste qui pourrait ˆetre int´eressante pour r´eduire ce travail inutile serait de signaler au programmeur, lors des premi`eres ex´ecutions d’un prototype de programme, que certaines parties de l’algorithme g´en`erent une grande quantit´e de travail inutile et que, par cons´equent, une r´e-´ecriture en prenant en compte cet ´etat de fait pourrait ´eviter ce travail. Il est mˆeme possible d’imaginer un outil proposant au programmeur une ´ebauche de solution pour l’aider `a restructurer une partie de son code afin d’´eviter ce travail inutile. Cependant, ce type d’outils ne peut rien pour aider `a ´eliminer le travail inutile intrins`eque `a l’algorithme. 43
  44. 44. 2.5 Conclusion Cette ´etude est, en premier lieu, une ´etude permettant de comprendre un ph´enom`ene, `a priori, contre intuitif : Le travail inutile. Pour ce faire, nous nous sommes bas´e sur des r´esultats existants qui ont d´ej`a ´et´e publi´es et qui montre que le travail inutile existe bel et bien dans des programmes classiques. Le but de ce stage ´etait d’´elargir les d´efinitions donn´ees dans ces articles afin d’avoir une id´ee du travail inutile global qui peut se trouver dans un programme. Cette ´etude, contrairement `a celles cit´ees ci-contre, n’avait pas pour but de trouver un moyen d’exploiter ce travail inutile pour en r´eduire l’impact sur le temps d’ex´ecution ou la consommation ´electrique mais seulement de comprendre ce ph´enom`ene et de savoir pourquoi ce travail inutile est pr´esent (est-ce du au compilateur ?, au programmeur ?. . .). En conclusion, nous pouvons dire que cette ´etude `a permis de confirmer l’existence du travail inutile et de comprendre, en partie, d’o`u il provient. 44
  45. 45. Bibliographie [1] G. Sohi A. Butt. Dynamic dead-instruction detection and elimination. ASPLOS X, October 2002. [2] Jeffrey D. Ullman Alfred V. Aho, Ravi Sethi. Compilers : Principles, Techniques and Tools. Addison-Wesley, 1986. [3] Gordon B. Bell. Characterization of silent stores. Submitted in partial fulfillment of the M.S. Degree in Electrical and Computer Engineering, May 2001. [4] F. Bodin. Cours d’optimisation : Transformer pour la performance. Septembre 2002. [5] M. Lipasti K. Lepak, G. Bell. Silent stores and store value locality. IEEE Transactions on Computers, 50(11), November 2001. [6] Mikko H. Lipasti Kevin M. Lepak. Temporally silent stores. ASPLOS X, October 2002. [7] Kevin M. Lepak. Silent stores for free : Reducing the cost of store verification. Submitted in partial fulfillment of the M.S. Degree in Electrical and Computer Engineering, December 2000. [8] Charles N. Fischer Milo M. Martin, Amir Roth. Exploiting dead value information. Proceedings of Micro-30, December 1997. 45

×