• Aucun résultat trouvé

Optimisation non classique : impl´ementation sans gla¸cons

`

A pr´esent on regarde comment se passer de gla¸cons en sortant un peu du cadre habituel des combinateurs dans le but d’ˆetre plus efficace.

Depuis qu’on fait rentrer les lambdas en les poussant de l’ext´erieur, on est sˆur que pour une expression IF(e1,e2,e3) dans une abstraction on va rencontrer dans le code compil´e une forme (Smn (Kn if ) e1 e2 e3 ...). Ce qui est remarquable c’est qu’on va rencontrer un combinateur S suivi d’un (K if ) suivi des transformations d’au moins e1, e2 et e3. Avec la strat´egie inner-most pour la combinatorisation, le S, le if et ses arguments auraient pu ˆetre s´epar´es.

Ici nous allons introduire un nouveau combinateur SIF. Plusieurs points motivent l’introduc-tion de ce nouveau combinateur :

– L’id´ee de remplacer (Smn (Kn if )...) par un seul combinateur n’est pas assez int´eressante car on peut d´ej`a penser qu’avec une bonne gestion des directions le K va disparaˆıtre.

– En revanche on peut esp´erer ne pas faire les n applications sur la branche du if qui ne sera jamais ´evalu´ee. Il faut pour cela stocker les arguments et, lorsqu’ils sont tous arriv´es, ´evaluer la condition (on a pu faire les applications partielles dessus au fur et `a mesure) puis ensuite appliquer la bonne branche aux arguments (section VI.3.2).

– Une fois qu’on a ce combinateur « paresseux » on peut regarder comment se passer de l’abstraction servant de gla¸con (section VI.3.3). En revanche pour les if n’´etant pas sous une abstraction il faut trouver autre chose.

VI.3.1 Combinateur d´edi´e au if

Extraction des (S3 (K if ))

Le terme λx.(if e1 e2 e3 a4 ... an) peut se combinatoriser en

Sn−3 (S3 (K if ) λx.e1 λx.e2 λx.e3) λx.a4 ... λx.an) par la chaˆıne suivante :

λx.(if e1 e2 e3 a4 ... an) = λx.(E a4 ... an) avec E = if e1 e2 e3 a4 ⇒ Sn−3 λx.E λx.a4 ... λx.an

⇒ Sn−3 (S3 (K if ) λx.e1 λx.e2 λx.e3) λx.a4 ... λx.an)

Cet exemple montre que d`es qu’on a un if sous un lambda, on peut extraire une forme (S3(K if )) sans voir exploser le reste du terme apr`es combinatorisation. (On peut toujours s´eparer un Sn en Si et Sn−i sans ajouter plus d’un combinateur, et de mˆeme pour Sn.)

Combinateur SIF

Le terme λx.(if e1 e2 e3) se combinatorise en (S3 (K if ) λx.e1 λx.e2 λx.e3). Si on utilise une variante de S3 qui ne distribue pas le lambda sur son premier argument (il s’agit de nyyy) on peut toujours obtenir (nyyy if λx.e1 λx.e2 λx.e3).

Posons un nouveau combinateur SIF qui est ´egal `a (nyyy if ) et tel que : λx.(if e1 e2 e3) ⇒ SIF λx.e1 λx.e2 λx.e3

Ce nouveau combinateur apporte une faible am´elioration en lui-mˆeme. La faible r´eduction de la taille du terme compil´e ne justifie pas forc´ement l’introduction d’un combinateur suppl´ementaire dans le langage cible. Toutefois cette ´etape nous semble n´ecessaire pour expliquer les optimisa-tions que nous pr´esentons dans le reste de cette section.

Le combinateur SIF se g´en´eralise aux combinateurs Sn

IF pour n abstractions d’un if.

VI.3.2 Optimisation du SIF

La r`egle de r´eduction du SIF donn´ee ci-dessus met en ´evidence une optimisation possible. En effet on voit que SIF distribue l’argument re¸cu `a tous les arguments di if. Or la distribution sur l’une des deux est n´ecessairement inutile. Ceci est d’autant plus vrai qu’on utilise SIFn . Nous proposons donc de retarder l’application des arguments jusqu’au moment o`u l’on saura quelle branche d´egeler afin de ne pas r´eduire d’applications inutiles. Ceci se r´ealise en impl´ementant SIF de la mani`ere suivante :

let comb S if =

fun e1 -> ValFun (fun e2 -> ValFun (fun e3 -> ValFun (fun a -> match app e1 a with

| ValInt i when i>0 -> app (app e2 a) dummy val | -> app (app e3 a) dummy val )))

Ainsi on est sˆur de r´eduire le minimum d’applications6. La r`egle de combinatorisation introdui-sant SIF est identique `a celle donn´ee ci-dessus.

G´en´eralisation `a SIFn

Ceci peut se g´en´eraliser `a SIFn en stockant les arguments re¸cus et en les passant `a la bonne branche une fois que tous ont ´et´e re¸cus.

Notons que si SIF ou mˆeme S est utilis´e alors qu’on avait l’opportunit´e d’utiliser un Sn IF

alors le r´esultat est toujours correct mais moins bien optimis´e.

VI.3.3 Abandon des gla¸cons sur les branches du IF

Les m´ethodes que nous avions propos´ees jusqu’`a pr´esent ne n´ecessitaient qu’un type HOterm tr`es simple o`u les fonctions, les fonctions non-strictes et les traits imp´eratifs se sont int´egr´es. Dans la section pr´esente, nous montrons que des optimisations sont possibles si on accepte de complexifier la syntaxe d’ordre sup´erieur et l’´evaluation associ´ee.

Les abstractions au dessus d’un if jouent le rˆole de gla¸cons naturels. Puisque nous nous sommes donn´e les moyens de ne passer les arguments d´egelant ces lambdas qu’`a la branche concern´ee par le d´egel, nous pouvons nous passer totalement de gla¸cons. Pour ce faire, nous n’introduirons plus de gla¸cons lors de la curryfication :

let rec traduit = function | ...

6

Les applications ´economis´ees n’´etaient que des applications partielles et l’´economie se r´eduit donc `a la cr´eation de clˆotures par le langage hˆote.

118 Optimisations du sch´ema d’´evaluation

| If (e1,e2,e3) ->

CApp ( CApp ( CApp (CExtFun ite, traduit e1), traduit e2) traduit e3)

et nous utiliserons l’impl´ementation suivante du combinateur SIF : let comb S if =

fun e1 -> ValFun (fun e2 -> ValFun (fun e3 -> ValFun (fun a -> match app e1 a with

| ValInt i when i>0 -> app e2 a | -> app e3 a ))

La r`egle de combinatorisation introduisant les SIF est celle qui est donn´ee ci-dessus (page 116). Cette optimisation a l’avantage de ne pas complexifier artificiellement la traduction des branches d’un if et ´egalement de n´ecessiter une r´eduction de moins dans chaque utilisation d’un if. Cette approche se g´en´eralise ´egalement aux Sn

IF.

Remarque : Cette m´ethode d’abandon des gla¸cons n’est correcte que si :

– On n’utilise jamais un S ou un Sn l`a o`u un SIF ou un SIFn aurait pu ˆetre utilis´e.

– Les abstractions sont distribu´ees sur toute expression contenant une application. Ceci signi-fie que les restrictions sur l’η-r´eduction et l’utilisation de B et C sont toujours n´ecessaires. Pour les if ne se trouvant pas prot´eg´es par une abstraction dans le programme source, on peut soit garder la technique des gla¸cons, soit faire en sorte que le if ne soit pas trait´e par le m´ecanisme de r´eduction d’application standard mais par un m´ecanisme d’application non-stricte. Par exemple on peut ajouter une construction dans le type HOterm :

type HOterm = | ...

| HOAppIF of HOterm * HOterm * HOterm

On utilisera donc HOAppIF(e1,e2,e3) au lieu de HOApp(HOApp(HOApp(HOFun ite, e1), e’2), e’3)o`u e’2 et e’3 sont les versions gel´ees de e2 et e3. L’´evaluation se fera comme suit :

let rec evalHO = function | ...

| AppIF(e1,e2,e3) -> (match eval e1 with

| ValInt i when i>0 -> evalHO e2 | -> evalHO e3 )

On aurait pu utiliser ce type de m´ecanisme depuis le d´ebut mais :

– On n’aurait pu le faire que pour les if n’´etant pas sous un lambda car la SK-traduction de λx.if (e1, e2, e3) n’est pas d´efinie.

– L’utilisation de gla¸cons nous a permis de rentrer dans le cadre de la SK-traduction et de l’associer aux traits imp´eratifs. Ceci nous a permis de raisonner tout au long des optimi-sations introduites et d’´elaborer des r`egles de traduction complexes mais intuitives.

– Enfin, pour chaque trait non-strict ou imp´eratif, il nous aurait fallu enrichir le type HOterm et pr´evoir un cas suppl´ementaire dans la fonction evalHO.

Regardons `a pr´esent comment on peut se passer des gla¸cons dans la combinatorisation des traits imp´eratifs du langage.

VI.3.4 Abandon des gla¸cons dans les traits imp´eratifs

L’affectation m´emoire ne n´ecessitait d´ej`a pas de gla¸con. En revanche, pour l’acc`es m´emoire, plusieurs cas sont `a examiner si on veut se passer de l’application qui d´eclenche l’acc`es m´emoire. Supposons que Get(m) dans le type term soit repr´esent´e dans la syntaxe curryfi´ee par une «constante » CGet(m). Analysons les trois cas pouvant se pr´esenter :

Acc`es m´emoire sous un lambda. On peut utiliser un K gelant comme celui d´ej`a ´evoqu´e en section VI.2.7, c’est-`a-dire un K qui embarque son premier argument pour l’´evaluer lorsque son deuxi`eme argument est arriv´e.

Dans ce cas on peut ne pas transformer un acc`es m´emoire en une application. On pourra combinatoriser Abs(x,CGet(m)) en Kget−m qui sera impl´ement´e par (fun -> assoc a !store).

Acc`es m´emoire sous un if. Nous avons vu qu’on pouvait impl´ementer un if qui d´eclenche litt´eralement l’´evaluation de la branche n´ecessaire. Donc si CGet(m) se trouve dans une branche then ou else, le code (assoc a !store) ne sera appel´e que si n´ecessaire.

Acc`es m´emoire sous rien. Si CGet(m) ne se trouve pas sous un lambda ni dans un if, l’acc`es `a la m´emoire peut se faire imm´ediatement. Le code (assoc a !store) convient donc.