This is documentation for Mathematica 4, which was
based on an earlier version of the Wolfram Language.
View current documentation (Version 11.2)

 Documentation /  Mathematica /  Das Mathematica Buch /  Die Prinzipien von Mathematica /  Evaluierung von Ausdrücken /

Fortgeschrittenes Thema: Unterbrechungen und AbbrücheFortgeschrittenes Thema: Manipulation des kompilierten Codes

2.5.14 Kompilieren von Mathematica-Ausdrücken

Wenn Sie eine Definition wie f[x_] := x Sin[x] aufstellen, wird Mathematica den Ausdruck x Sin[x] in einer Form ablegen, die für ein beliebiges x evaluiert werden kann. Wenn Sie dann für x einen Wert angeben, wird Mathematica diesen Wert in x Sin[x] einsetzen und das Ergebnis evaluieren. Der von Mathematica für diese Evaluierung eingesetzte interne Code ist so konstruiert, daß er genausogut funktioniert, ob nun der von Ihnen angegebene Wert für x eine Zahl, eine Liste, ein algebraisches Objekt oder irgendein anderer Ausdruck ist.

Wenn man all diese Möglichkeiten berücksichtigen muß, wird das Evaluierungsverfahren unweigerlich langsamer. Wenn jedoch Mathematica annehmen könnte, daß x eine Maschinenzahl ist, dann könnte es viele Schritte vermeiden und einen Ausdruck wie x Sin[x] möglicherweise sehr viel schneller evaluieren.

Mit Compile können Sie kompilierte Funktionen in Mathematica konstruieren, die Mathematica-Ausdrücke unter der Annahme evaluieren, daß alle auftretenden Parameter Zahlen (oder logische Variablen) sind. Compile[, , ... , ausdr] übernimmt einen Ausdruck ausdr und gibt eine „kompilierte Funktion" zurück, die diesen Ausdruck bei Angabe der Argumente , , ... evaluiert.

Im allgemeinen erzeugt Compile ein CompiledFunction-Objekt, das eine Folge einfacher Anweisungen zur Evaluierung der kompilierten Funktion enthält. Die Anweisungen sind so gewählt, daß sie denen ähneln, die man im Maschinencode eines typischen Computers findet, und die deshalb schnell ausgeführt werden können.

Erzeugung kompilierter Funktionen

Dies definiert f als eine reine Funktion, die x Sin[x] für ein beliebiges x evaluiert.

In[1]:= f = Function[{x}, x Sin[x]]

Out[1]=

Dies erzeugt eine kompilierte Funktion zur Evaluierung von x Sin[x].

In[2]:= fc = Compile[{x}, x Sin[x]]

Out[2]=

f und fc liefern dieselben Ergebnisse, aber fc läuft schneller, wenn das von Ihnen angegebene Argument eine Zahl ist.

In[3]:= {f[2.5], fc[2.5]}

Out[3]=

Compile ist nützlich, wenn Sie einen numerischen oder logischen Ausdruck häufig evaluieren müssen. Nehmen Sie sich die Zeit, Compile aufzurufen, so können Sie eine kompilierte Funktion erhalten, die schneller als eine gewöhnliche Mathematica-Funktion ausgeführt werden kann.

Bei einfachen Ausdrücken wie x Sin[x] besteht gewöhnlich kaum ein Unterschied zwischen der Ausführungsgeschwindigkeit für gewöhnliche und für kompilierte Funktionen. Mit wachsendem Umfang der beteiligten Ausdrücke werden jedoch die Vorteile der Kompilierung größer. Bei großen Ausdrücken kann die Kompilierung die Ausführung um einen Faktor 20 beschleunigen.

Kompilierung ergibt die größten Verbesserungen bei Ausdrücken, die eine große Anzahl einfacher, sagen wir, arithmetischer Funktionen enthalten. Bei komplizierteren Funktionen, wie zum Beispiel BesselK oder Eigenvalues, wird die meiste Rechenzeit mit der Ausführung interner Mathematica-Algorithmen verbracht, auf die sich die Kompilierung nicht auswirkt.

Dies erzeugt eine kompilierte Funktion zur Ermittlung von Werten des zehnten Legendre-Polynoms. Mit Evaluate erhält Mathematica die Anweisung, das Polynom explizit vor einer Kompilierung zu konstruieren.

In[4]:= pc = Compile[{x}, Evaluate[LegendreP[10, x]]]

Out[4]=

Dies ermittelt den Wert des zehnten Legendre-Polynoms mit dem Argument 0.4.

In[5]:= pc[0.4]

Out[5]=

Dies verwendet den eingebauten numerischen Code.

In[6]:= LegendreP[10, 0.4]

Out[6]=

Auch wenn Sie Kompilierung zur Beschleunigung von Ihnen geschriebener numerischer Funktionen verwenden können, sollten Sie dennoch versuchen, möglichst eingebaute Mathematica-Funktionen einzusetzen. Eingebaute Funktionen laufen gewöhnlich schneller als alle von Ihnen erstellten und kompilierten Mathematica-Programme. Zusätzlich verwenden diese in der Regel extensivere Algorithmen mit vollständigerer Kontrolle über numerische Präzision usw.

Sie sollten wissen, daß eingebaute Mathematica-Funktionen sehr häufig selbst Compile einsetzen. So wendet zum Beispiel NIntegrate in Voreinstellung automatisch Compile auf den Ausdruck an, den Sie zur Integration angeben. Funktionen wie Plot und Plot3D wenden Compile ebenso auf die Ausdrücke an, die Sie zum Zeichnen übergeben. Eingebaute Funktionen, die Compile verwenden, haben in der Regel die Option Compiled. Durch Festlegung von Compiled -> False werden die Funktionen angewiesen, Compile nicht zu verwenden.

Angabe der Typen für die Kompilierung

Compile funktioniert, indem es Annahmen über die Objekt-Typen aufstellt, die bei der Evaluierung des von Ihnen angegebenen Ausdrucks auftreten. Die Vorgabe-Annahme ist, daß alle Variablen im Ausdruck reelle Gleitpunktzahlen sind.

Compile läßt aber dennoch ganze Zahlen, komplexe Zahlen und logische Variablen (True oder False) sowie Zahlen-Arrays zu. Der Typ einer Variablen kann durch Angabe eines Musters spezifiziert werden, das nur zu solchen Werten paßt, die von diesem Typ sind. Sie können so zum Beispiel das Muster _Integer zur Spezifizierung des Typs „ganze Zahl" verwenden. Ebenso läßt sich mit True | False eine logische Variable spezifizieren, die entweder True oder False sein muß.

Dies kompiliert den Ausdruck 5 i + j unter der Annahme, daß i und j ganze Zahlen sind.

In[7]:= Compile[{{i, _Integer}, {j, _Integer}}, 5 i + j]

Out[7]=

Dies liefert ein ganzzahliges Ergebnis.

In[8]:= %[8, 7]

Out[8]=

Dies kompiliert einen Ausdruck, der eine Operation mit einer Matrix mit ganzen Zahlen durchführt.

In[9]:= Compile[{{m, _Integer, 2}}, Apply[Plus, Flatten[m]]]

Out[9]=

Die Listen-Operationen werden jetzt in kompilierter Form ausgeführt, und das Ergebnis ist eine ganze Zahl.

In[10]:= %[{{1, 2, 3}, {7, 8, 9}}]

Out[10]=

Die Typen, die Compile verarbeitet, entsprechen im Grunde den Typen, mit denen Computer auf einem Maschinen-Code-Niveau normalerweise arbeiten. So kann Compile zum Beispiel reelle Gleitpunktzahlen mit Maschinengenauigkeit, aber keine Zahlen mit beliebiger Präzision bearbeiten. Wenn Sie außerdem angeben, daß eine bestimmte Variable eine ganze Zahl ist, so erzeugt Compile nur für den Fall Code, daß die ganze Zahl „Maschinengröße" hat (in der Regel zwischen ).

Wenn der zu kompilierende Ausdruck nur normale arithmetische und logische Operationen enthält, kann Compile die bei jedem Schritt erzeugten Objekt-Typen einfach aus den Typen der Eingabevariablen ableiten. Wenn Sie jedoch andere Funktionen aufrufen, wird Compile in der Regel nicht wissen, welchen Wertetyp diese zurückgeben. Wenn Sie nichts anderes angeben, geht Compile davon aus, daß jede andere Funktion eine reelle Gleitpunktzahl ergibt. Sie können jedoch auch eine explizite Liste mit Mustern angeben, die spezifiziert, welcher Typ für einen Ausdruck anzunehmen ist, der zu einem bestimmten Muster paßt.

Dies definiert eine Funktion, die ein ganzzahliges Ergebnis liefert, wenn sie ein ganzzahliges Argument erhält.

In[11]:= com[i_] := Binomial[2i, i]

Dies kompiliert x^com[i] unter der Annahme, daß com[_] immer eine ganze Zahl ist.

In[12]:= Compile[{x, {i, _Integer}}, x^com[i],
{{com[_], _Integer}}]

Out[12]=

Dies evaluiert die kompilierte Funktion.

In[13]:= %[5.6, 1]

Out[13]=

Hinter Compile steckt die Idee, eine Funktion zu erzeugen, die für gewisse Argumenttypen optimiert ist. Compile ist aber trotzdem so angelegt, daß die erzeugten Funktionen beliebige Argumenttypen akzeptieren. Kann die Optimierung nicht verwendet werden, so wird ein gewöhnlicher Mathematica-Ausdruck evaluiert, um den Funktionswert zu finden.

Dies ist eine kompilierte Funktion für das Ziehen der Quadratwurzel einer Variablen.

In[14]:= sq = Compile[{x}, Sqrt[x]]

Out[14]=

Wenn Sie für das Argument eine reelle Zahl angeben, so wird optimierter Code verwendet.

In[15]:= sq[4.5]

Out[15]=

Der kompilierte Code kann nicht verwendet werden, deshalb druckt Mathematica eine Warnung und evaluiert dann einfach den ursprünglichen symbolischen Ausdruck.

In[16]:= sq[1 + u]

Out[16]=

Der von Compile erzeugte Code muß nicht nur über die Typen der Argumente, die Sie angeben, Annahmen machen, sondern auch über die Typen all jener Objekte, die bei Ausführung des Codes erscheinen. Mitunter hängen diese Typen von den konkreten Werten der von Ihnen spezifizierten Argumente ab. Deshalb liefert zum Beispiel Sqrt[x] für reelle x als Ergebnis eine reelle Zahl, wenn x nicht negativ ist, aber eine komplexe Zahl, wenn x negativ ist.

Compile macht immer eine eindeutige Annahme über den von einer bestimmten Funktion zurückgegebenen Typ. Stellt sich diese Annahme bei Ausführung des von Compile erzeugten Codes als unzutreffend heraus, dann gibt Mathematica den kompilierten Code in diesem Fall einfach auf und evaluiert stattdessen einen gewöhnlichen Mathematica-Ausdruck, um das Ergebnis zu erhalten.

Der kompilierte Code erwartet keine komplexe Zahl, deshalb muß Mathematica den ursprünglichen symbolischen Ausdruck wieder explizit evaluieren.

In[17]:= sq[-4.5]

Out[17]=

Eine wichtige Eigenschaft von Compile ist, daß es nicht nur auf mathematische Ausdrücke, sondern auch auf diverse einfache Mathematica-Programme anwendbar ist. Deshalb kann zum Beispiel Compile bedingte Anweisungen und Kontrollfluß-Strukturen verarbeiten.

In allen Fällen hält Compile[vars, ausdr] seine Argumente unevaluiert. Deshalb können Sie ein „Programm" explizit als zu kompilierenden Ausdruck angeben.

Dies erzeugt eine kompilierte Version eines Mathematica-Programmes, das das Newtonsche Näherungsverfahren für Quadratwurzeln implementiert.

In[18]:= newt = Compile[ {x, {n, _Integer}},
Module[{t}, t = x; Do[t = (t + x/t)/2, {n}]; t]
]

Out[18]=

Dies führt den kompilierten Code aus.

In[19]:= newt[2.4, 6]

Out[19]=

Fortgeschrittenes Thema: Unterbrechungen und AbbrücheFortgeschrittenes Thema: Manipulation des kompilierten Codes