Haben funktionale Programmiersprachen mehr Möglichkeiten zur Optimierung der Kompilierungszeit?

10

Ich las das Buch "Funktionale Programmierung für die reale Welt". Es begann mit dem Vergleich zwischen imperativen und funktionalen Programmiersprachen. Und es wurde angegeben, wie sich "Werte" und "Ausdrücke" in der funktionalen Programmierung von "Variablen" und "Funktionen" der imperativen Programmierung unterscheiden. Aus der Diskussion entwickelte ich eine Idee, dass -

Funktionale Programmiersprachen bieten mehr Möglichkeiten zur Optimierung der Kompilierungszeit als ihre zwingenden Gegenstücke.

Ist es wahr?

Gulshan
quelle

Antworten:

16

Funktionale Programmiersprachen optimieren die Kompilierungszeit wesentlich besser. Einer der Gründe ist die Reinheit - Parallelität ist trivial, weil es keinen Zustand gibt. Der Compiler kann also zwei Zweige nehmen und sie einfach zusammenfassen, ohne das Verhalten des Programms zu ändern.

Gleichzeitig kann alles, was ohne Status berechnet werden kann (dh alles, was in haskell nicht monadisch ist), vom Compiler im Voraus berechnet werden. Solche Berechnungen können jedoch teuer sein und werden daher wahrscheinlich nur teilweise durchgeführt.

Darüber hinaus kann alles, was nicht rechnerisch benötigt wird, vollständig aus dem Programm heraus optimiert werden.

Alternative
quelle
1
+1: Die nebenwirkungsfreie Programmierung ist sehr, sehr einfach zu optimieren.
S.Lott
@mathepic: Tatsächlich erfolgt die Parallelisierung (in Haskell) sowohl zur Kompilierungszeit als auch zur Laufzeit. Die Kompilierungszeit entscheidet, ob es sich lohnt, einen Shard (Thread-Seed) zu erstellen, und die Laufzeit verarbeitet Shards so schnell wie möglich, abhängig von der Anzahl der von Ihnen angegebenen Threads. Wenn Sie nur einen einzelnen Thread angeben, werden die Shards erstellt, aber nacheinander verarbeitet.
Matthieu M.
2
@mathepic: eine andere Verwendung von Reinheit -> Faulheit und das Fehlen von Berechnungen. Wenn Sie nachweisen können, dass der Wert nicht benötigt wird, entfernen Sie die Berechnung vollständig. Wenn es benötigt wird, verwenden Sie einen faulen Thunk. Wenn Sie wissen, dass es benötigt wird, berechnen Sie es direkt (um den faulen Overhead zu vermeiden). Reinheit ist (nur) erstaunlich :)
Matthieu M.
@ Matthieu guter Punkt
Alternative
1
@ Masse Auch die IO-Monade ist rein. Es ist nur so, dass das Ergebnis des "Ausführens" der E / A-Monade nicht ist.
Alternative
7

Dass es für funktionale Sprachen im Prinzip mehr Möglichkeiten zur Optimierung der Kompilierungszeit gibt als für ihre zwingenden Gegenstücke, ist wahrscheinlich richtig.

Interessanter ist jedoch, ob sie tatsächlich in aktuellen Compilern implementiert sind und wie relevant diese Optimierungen in der Praxis sind (dh die endgültige Leistung von idiomatischem 'Real Life (TM)' - Code in einer Produktionsumgebung mit a priori vorhersehbaren Compilereinstellungen).

Zum Beispiel erwecken die Haskell-Beiträge für das berüchtigte Computersprachen-Benchmark-Spiel (so schlecht es auch sein mag - aber es ist nicht so, dass es im Moment etwas wesentlich Besseres gibt) den Eindruck, dass viel Zeit dafür aufgewendet wurde Manuelle Optimierungen, die mit der Behauptung "mögliche Compiler-Optimierungen aufgrund von insert some property about FP languages here" konfrontiert wurden, lassen es so aussehen, als wären die Optimierungen (derzeit zumindest) eher eine theoretische Möglichkeit als eine relevante Realität.

Ich würde mich freuen, wenn ich mich in diesem Punkt als falsch erweisen würde.

Alexander Battisti
quelle
1
Der Grund für die manuellen Optimierungen in Haskell hat mehr damit zu tun, dass bestimmte "unkomplizierte" Vorgänge in Haskell (aus Komplexitätssicht) etwas zeitaufwändig sind. Angenommen, Sie möchten das letzte Element einer Liste abrufen. In Java ist das eine ziemlich einfache Operation. In Haskell erfordert der naive Ansatz, dass Sie die gesamte Liste bis zum Ende durchlaufen (aufgrund der Faulheit der Listen), was sie zu einer O (n) -Operation macht. Hier kommen (teilweise) manuelle Optimierungen ins
Spiel
2
Ich bin sicher, dass es triftige Gründe für Haskells handgerollte Optimierungen gibt, aber sie sind für "unkomplizierte" Operationen notwendig und vermitteln den Eindruck, dass die größeren Möglichkeiten zur Optimierung von Code (derzeit) nur theoretisch relevant sind.
Alexander Battisti
6
Es ist eher der Unterschied zwischen der Optimierung von Algorithmen und der Optimierung des generierten Codes. Nehmen Sie ein C-Beispiel: Wenn ich einen Suchalgorithmus schreibe, kann ich einen naiven Algorithmus schreiben, der einfach durch eine Liste geht, oder ich kann die binäre Suche verwenden. In beiden Fällen optimiert der Compiler meinen Code, verwandelt jedoch einen naiven Suchalgorithmus nicht in eine binäre Suche. Viele der manuellen Optimierungen im Haskell-Code haben mehr mit der Optimierung der Algorithmen selbst zu tun als mit der Optimierung des generierten Codes.
Mipadi
2
@mipadi, aber es scheint, dass die einfache Version des Haskell-Algorithmus nicht so gut ist wie die einfache Version der Algorithmen anderer Sprachen. (Ich vermute, dass das Funktionsmodell und die Computerarchitektur nicht übereinstimmen.) Auch wenn es gut darin ist, Code zu generieren, reicht es nicht aus, um dieses Problem zu lösen.
Winston Ewert
>> schlecht wie es sein könnte << - Weißt du ob es schlecht ist oder nicht? Was meinst du überhaupt damit, dass es "schlecht" ist? Bitte erläutern.
igouy
2

Im funktionalen Stil ist der Wertefluss durch ein Programm sehr, sehr sichtbar (sowohl für den Compiler als auch für den Programmierer). Dies gibt dem Compiler viel Spielraum, um zu entscheiden, wo Werte gespeichert werden sollen, wie lange sie aufbewahrt werden sollen usw.

In einer imperativen Sprache verspricht der Compiler dem Programmierer ein Modell, bei dem die meisten Variablen tatsächlichen Speicherorten entsprechen, die für eine definierte Lebensdauer bestehen bleiben. Möglicherweise kann jede Anweisung von jedem dieser Speicherorte lesen (oder darauf schreiben!), Sodass der Compiler nur Speicherorte durch Registerzuordnung ersetzen, zwei Variablen zu einem einzigen Speicherort zusammenführen oder ähnliche Optimierungen durchführen kann, nachdem eine sorgfältige Analyse des Speicherorts durchgeführt wurde Andernfalls kann im Programm auf diese Variable verwiesen werden.

Nun gibt es zwei Einschränkungen:

  • Die Programmiersprachen-Community hat in den letzten fünfzig Jahren viel Mühe darauf verwendet, clevere Methoden für diese Analyse zu entwickeln. Es gibt bekannte Tricks wie das Färben von Registern usw., um einige der einfacheren Fälle die meiste Zeit zu erledigen. Dies führt jedoch zu großen, langsamen Compilern und einem ständigen Kompromiss zwischen der Komplexität der Kompilierung und der Qualität des resultierenden Codes
  • Gleichzeitig sind die meisten funktionalen Programmiersprachen auch nicht rein funktional. Viele der Dinge, die Programme tatsächlich tun müssen, wie das Reagieren auf E / A, sind von Natur aus nicht funktionsfähig, sodass kein Compiler völlig frei von diesen Tricks sein kann und keine Sprache sie vollständig vermeidet - sogar Haskell, was auch ein bisschen ist rein für meinen Geschmack (Ihr Kilometerstand kann variieren) kann nur die nicht funktionierenden Teile Ihres Codes kontrollieren und abschotten, nicht ganz vermeiden.

Um die allgemeine Frage zu beantworten: Ja, ein funktionales Paradigma gibt dem Compiler viel Freiheit bei der Optimierung, die er in einer zwingenden Umgebung nicht hat.

jimwise
quelle
Alles in Haskell ist funktional, es ist nur so, dass maines sich um eine Zustandsumwandlung handelt und nicht um etwas, das den Zustand selbst verwendet.
Alternative
Ja und nein - konzeptionell ist es völlig richtig zu sagen, dass ein Haskell-Programm eine reine Funktion über die Interaktionen des Benutzers mit diesem Programm ist (und den Status des Zufallszahlengenerators des Systems und die Latenz des Netzwerks heute und alle anderen inhärenten nicht funktionierende Eingaben, auf die das Programm reagiert), aber in der Praxis ist es eine Unterscheidung ohne Unterschied.
Jimwise
@jimwise Referenzielle Transparenz ist ein sehr großer Unterschied.
Alternative
Abgesehen davon, dass Sie es nicht wirklich haben, zumindest für den hier diskutierten Zweck. Der Punkt von Operationen wie E / A oder Zufallszahlengenerierung ist, dass sie jedes Mal, wenn sie mit denselben Eingaben aufgerufen werden , einen anderen Wert zurückgeben sollten, und dies schränkt die funktionale Argumentation für den Programmierer oder den Compiler von Natur aus ein .
Jimwise
1
@mathepic Ja, natürlich können Sie Zufälligkeit oder Benutzereingaben (oder Netzwerklatenz oder Systemlast) konzeptionell als unendlichen Stream oder als Funktion von Status zu Status betrachten, aber es ist keine Ansicht, die sich für nützliche Überlegungen zum Programmverhalten von eignet Sie oder Ihr Compiler - Bob Harper behandelt diesen Grund in einem kürzlich veröffentlichten Beitrag in seinem Blog über CMUs neuen CS-Lehrplan für funktionale Programmierung.
Jimwise