|
2.5.14 Compilation d'expressions Mathematica
Si vous donnez une définition comme f[x_] := x Sin[x], Mathematica enregistre l'expression x Sin[x] sous une forme qui peut être évaluée pour tout x. Ensuite, lorsque vous donnez une valeur particulière à x, Mathematica remplace cette valeur dans x Sin[x] et évalue le résultat. Le code interne que Mathematica utilise pour exécuter cette évaluation est défini de façon à fonctionner de la même manière si la valeur que vous donnez à x est un nombre, une liste, un objet algébrique ou tout autre type d'expression.
Le fait de devoir tenir compte de toutes ces possibilités ralentit inévitablement le processus d'évaluation. Toutefois, si Mathematica pouvait supposer que x est un nombre machine, il pourrait éviter de nombreuses étapes et potentiellement évaluer une expression comme x Sin[x] nettement plus rapidement.
L'utilisation de la fonction Compile vous permet de construire des fonctions compilées dans Mathematica, qui évaluent des expressions Mathematica en supposant que tous les paramètres qui apparaissent sont des nombres (ou des variables logiques). Compile[ , , ... , expr] prend une expression expr et renvoie une "fonction compilée" qui évalue cette expression lorsqu'on lui donne les arguments , , ... .
En général, la fonction Compile crée un objet CompiledFunction qui contient une suite d'instructions simples pour évaluer la fonction compilée. Les instructions sont choisies de manière à être proches de celles figurant dans le code machine d'un ordinateur standard et peuvent être ainsi exécutées rapidement.
Création de fonctions compilées.
Ceci définit f comme une fonction pure qui évalue x Sin[x] pour tout x.
In[1]:= f = Function[{x}, x Sin[x]]
Out[1]= 
Ceci crée une fonction compilée pour évaluer x Sin[x].
In[2]:= fc = Compile[{x}, x Sin[x]]
Out[2]= 
Les expressions f et fc renvoient les mêmes résultats, mais fc s'exécute plus rapidement lorsque l'argument que vous donnez est un nombre.
In[3]:= {f[2.5], fc[2.5]}
Out[3]= 
La fonction Compile est utile dans des situations où vous devez évaluer plusieurs fois une expression numérique ou logique particulière. Le fait de prendre le temps d'appeler Compile vous permet d'obtenir une fonction compilée qui peut être exécutée plus rapidement qu'une fonction Mathematica ordinaire.
Pour des expressions simples comme x Sin[x], la vitesse d'exécution pour des fonctions ordinaires et compilées ne varie pratiquement pas. Toutefois, à mesure que la taille des expressions impliquées augmente, l'avantage de la compilation augmente également. Pour des expressions de grande taille, la compilation peut accélérer l'exécution d'un facteur aussi grand que 20.
L'avantage apporté par la compilation se retrouve surtout dans les expressions contenant un grand nombre de fonctions simples, par exemple arithmétiques. Pour des fonctions plus compliquées comme les fonctions BesselK ou Eigenvalues, la majeure partie du temps de calcul est utilisée pour l'exécution d'algorithmes Mathematica internes, sur lesquels la compilation n'a aucun effet.
Ceci crée une fonction compilée permettant de trouver les valeurs du dixième polynôme de Legendre. Evaluate indique à Mathematica de construire le polynôme explicitement avant de compiler.
In[4]:= pc = Compile[{x}, Evaluate[LegendreP[10, x]]]
Out[4]= 
Ceci trouve la valeur du dixième polynôme de Legendre avec l'argument 0.4.
In[5]:= pc[0.4]
Out[5]= 
Ceci utilise le code numérique interne.
In[6]:= LegendreP[10, 0.4]
Out[6]= 
Même si vous pouvez utiliser la compilation pour accélérer l'exécution de fonctions numériques que vous écrivez, vous devriez essayer d'utiliser des fonctions Mathematica internes chaque fois que c'est possible. Les fonctions internes s'exécutent d'habitude plus rapidement que tous les programmes Mathematica compilés que vous créez. De plus, elles utilisent des algorithmes plus puissants, avec un meilleur contrôle de la précision numérique et ainsi de suite.
Sachez que les fonctions Mathematica internes utilisent souvent elles-mêmes la fonction Compile. Ainsi, par exemple, la fonction NIntegrate utilise par défaut la fonction Compile sur l'expression que vous donnez à intégrer. De même, des fonctions comme Plot et Plot3D utilisent Compile sur les expressions qu'elles doivent tracer. Les fonctions internes qui utilisent Compile ont typiquement l'option Compiled activée. La valeur Compiled -> False indique aux fonctions de ne pas utiliser Compile.
Spécification de types pour la compilation.
Compile fonctionne en effectuant des hypothèses sur les types d'objets qui apparaissent dans l'évaluation de l'expression que vous donnez. L'hypothèse par défaut est que toutes les variables contenues dans l'expression sont des réels approchés.
Toutefois, Compile permet d'utiliser des entiers, des complexes et des variables logiques (True ou False), ainsi que des tableaux de nombres. Vous pouvez indiquer le type d'une variable particulière en indiquant une forme qui ne correspond qu'à des valeurs ayant ce type. Ainsi, par exemple, vous pouvez utiliser la forme _Integer pour spécifier le type entier. De même, vous pouvez utiliser True | False pour spécifier une variable logique qui doit prendre la valeur True ou False.
Ceci compile l'expression 5 i + j en supposant que i et j sont des entiers.
In[7]:= Compile[{{i, _Integer}, {j, _Integer}}, 5 i + j]
Out[7]= 
Ceci renvoie un résultat entier.
In[8]:= %[8, 7]
Out[8]= 
Ceci compile une expression qui exécute une opération sur une matrice d'entiers.
In[9]:= Compile[{{m, _Integer, 2}}, Apply[Plus, Flatten[m]]]
Out[9]= 
Les opérations sur la liste sont à présent exécutées de façon compilée, et le résultat est un entier.
In[10]:= %[{{1, 2, 3}, {7, 8, 9}}]
Out[10]= 
Les types que Compile traite correspondent principalement aux types que les ordinateurs traitent au niveau du code machine. Ainsi, par exemple, la fonction Compile peut traiter des nombres réels approchés qui ont une précision machine, mais ne peut pas traiter des nombres en précision arbitraire. De plus, si vous indiquez qu'une variable donnée est un entier, Compile ne produit du code que dans le cas où l'entier est bien en "taille machine", c'est-à-dire typiquement entre .
Lorsque l'expression à compiler ne comporte que des opérations arithmétiques et logiques standard, Compile peut déduire les types d'objets produits à chaque étape simplement à partir des types des variables d'entrée. Toutefois, si vous appelez d'autres fonctions, Compile ne connaîtra normalement pas quel type de valeur elles renvoient. Si vous n'indiquez rien, la fonction Compile suppose que toute autre fonction renvoie une valeur réelle approchée. Toutefois, vous pouvez également donner une liste explicite de formes, indiquant quel type déduire pour une expression correspondant à une forme donnée.
Ceci définit une fonction qui renvoie un entier lorsque vous indiquez un argument entier.
In[11]:= com[i_] := Binomial[2i, i]
Ceci compile x^com[i] en supposant que com[_] est toujours un entier.
In[12]:= Compile[{x, {i, _Integer}}, x^com[i], {{com[_], _Integer}}]
Out[12]= 
Ceci évalue la fonction compilée.
In[13]:= %[5.6, 1]
Out[13]= 
L'idée de Compile est de créer une fonction qui soit optimisée pour certains types d'arguments. Toutefois, la fonction Compile est configurée de manière à ce que les fonctions qu'elle crée fonctionnent quels que soient les types des arguments qu'on leur donne. Lorsque l'optimisation ne peut pas être utilisée, une expression Mathematica standard est évaluée pour trouver la valeur de la fonction.
Ci-dessous une fonction compilée calculant la racine carrée d'une variable.
In[14]:= sq = Compile[{x}, Sqrt[x]]
Out[14]= 
Si vous donnez un nombre réel comme argument, le code optimisé est utilisé.
In[15]:= sq[4.5]
Out[15]= 
Le code compilé ne peut être utilisé, et donc Mathematica affiche un avertissement puis évalue simplement l'expression symbolique d'origine.
In[16]:= sq[1 + u]

Out[16]= 
Le code compilé produit par Compile doit effectuer des hypothèses non seulement concernant les types des arguments que vous donnez, mais également à propos des types de tous les objets qui apparaissent pendant l'exécution du code. Parfois, ces types dépendent des valeurs effectives des arguments que vous spécifiez. Ainsi, par exemple, Sqrt[x] renvoie un résultat réel pour x réel si x n'est pas négatif, mais renvoie un nombre complexe si x est négatif.
Compile fait toujours une hypothèse définie concernant le type renvoyé par une fonction donnée. Si cette hypothèse s'avère invalide dans un cas particulier lorsque le code produit par Compile est exécuté, Mathematica abandonne simplement le code compilé dans ce cas et évalue une expression Mathematica ordinaire pour obtenir le résultat.
Le code compilé n'attend pas un nombre complexe, et donc Mathematica doit revenir à l'évaluation explicite de l'expression symbolique d'origine.
In[17]:= sq[-4.5]

Out[17]= 
Une caractéristique importante de la fonction Compile est qu'elle peut traiter non seulement des expressions mathématiques, mais également divers programmes Mathematica simples. Ainsi, par exemple, Compile peut traiter des structures conditionnelles et de flux de contrôle.
Dans tous les cas, Compile[vars, expr] conserve ses arguments non évalués. Cela signifie que vous pouvez explicitement donner un "programme" comme expression à compiler.
Ceci crée une version compilée d'un programme Mathematica qui implémente l'approximation de Newton des racines carrées.
In[18]:= newt = Compile[ {x, {n, _Integer}}, Module[{t}, t = x; Do[t = (t + x/t)/2, {n}]; t] ]
Out[18]= 
Ceci exécute le code compilé.
In[19]:= newt[2.4, 6]
Out[19]= 
|