Ich stimme Dietrich Epp zu: Es ist eine Kombination mehrerer Dinge , die GHC schnell machen.
In erster Linie ist Haskell sehr hochrangig. Auf diese Weise kann der Compiler aggressive Optimierungen durchführen, ohne den Code zu beschädigen .
Denken Sie an SQL. Wenn ich jetzt eine SELECT
Aussage schreibe , sieht sie vielleicht wie eine Imperativschleife aus, ist es aber nicht . Es sieht möglicherweise so aus, als würde es alle Zeilen in dieser Tabelle durchlaufen, um diejenige zu finden, die den angegebenen Bedingungen entspricht, aber tatsächlich könnte der "Compiler" (die DB-Engine) stattdessen eine Indexsuche durchführen - die völlig andere Leistungsmerkmale aufweist. Aber weil SQL so hoch ist, kann der "Compiler" völlig unterschiedliche Algorithmen ersetzen, mehrere Prozessoren oder E / A-Kanäle oder ganze Server transparent anwenden und vieles mehr.
Ich denke, Haskell ist dasselbe. Sie könnten denken, Sie haben Haskell gerade gebeten, die Eingabeliste einer zweiten Liste zuzuordnen, die zweite Liste in eine dritte Liste zu filtern und dann zu zählen, wie viele Elemente sich ergeben haben. Aber Sie haben nicht gesehen, dass GHC hinter den Kulissen Regeln für das Umschreiben von Stream-Fusion angewendet hat, die das Ganze in eine einzige enge Maschinencodeschleife verwandeln, die den gesamten Job in einem einzigen Durchgang über die Daten ohne Zuordnung erledigt - so etwas würde es tun mühsam, fehleranfällig und nicht wartbar sein, um von Hand zu schreiben. Das ist nur wirklich möglich, weil der Code keine Details auf niedriger Ebene enthält.
Eine andere Sichtweise könnte sein: Warum sollte Haskell nicht schnell sein? Was macht es, das es langsam machen sollte?
Es ist keine interpretierte Sprache wie Perl oder JavaScript. Es ist nicht einmal ein virtuelles Maschinensystem wie Java oder C #. Es wird bis zum nativen Maschinencode kompiliert, sodass dort kein Overhead entsteht.
Im Gegensatz zu OO-Sprachen [Java, C #, JavaScript…] verfügt Haskell über eine vollständige Löschung [wie C, C ++, Pascal…]. Alle Typprüfungen finden nur zur Kompilierungszeit statt. Es gibt also auch keine Laufzeit-Typprüfung, die Sie verlangsamt. (Für diese Angelegenheit gibt es keine Nullzeigerprüfungen. In Java muss die JVM beispielsweise nach Nullzeigern suchen und eine Ausnahme auslösen, wenn Sie eine zurückstellen. Haskell muss sich nicht um diese Prüfung kümmern.)
Sie sagen, es klingt langsam, "Funktionen zur Laufzeit im laufenden Betrieb zu erstellen", aber wenn Sie genau hinschauen, tun Sie das nicht wirklich. Es könnte so aussehen wie Sie, aber Sie tun es nicht. Wenn Sie sagen (+5)
, ist das in Ihrem Quellcode fest codiert. Es kann sich zur Laufzeit nicht ändern. Es ist also keine wirklich dynamische Funktion. Sogar Curry-Funktionen speichern wirklich nur Parameter in einem Datenblock. Der gesamte ausführbare Code ist zur Kompilierungszeit tatsächlich vorhanden. Es gibt keine Laufzeitinterpretation. (Im Gegensatz zu einigen anderen Sprachen, die eine "Bewertungsfunktion" haben.)
Denken Sie an Pascal. Es ist alt und niemand benutzt es wirklich mehr, aber niemand würde sich beschweren, dass Pascal langsam ist . Es gibt viele Dinge, die man nicht mögen kann, aber Langsamkeit ist nicht wirklich eine davon. Haskell macht nicht wirklich so viel, was sich von Pascal unterscheidet, abgesehen von einer Speicherbereinigung anstelle einer manuellen Speicherverwaltung. Unveränderliche Daten ermöglichen mehrere Optimierungen der GC-Engine [was eine verzögerte Auswertung dann etwas erschwert].
Ich denke, die Sache ist, dass Haskell fortschrittlich und raffiniert und auf hohem Niveau aussieht , und jeder denkt: "Oh wow, das ist wirklich mächtig, es muss erstaunlich langsam sein! " Aber das ist es nicht. Zumindest ist es nicht so, wie Sie es erwarten würden. Ja, es hat ein erstaunliches Typensystem. Aber weißt du was? Das alles passiert zur Kompilierungszeit. Zur Laufzeit ist es weg. Ja, Sie können damit komplizierte ADTs mit einer Codezeile erstellen. Aber weißt du was? Ein ADT ist nur ein einfaches gewöhnliches C union
von struct
s. Nichts mehr.
Der wahre Killer ist die faule Bewertung. Wenn Sie die Strenge / Faulheit Ihres Codes richtig eingestellt haben, können Sie dumm schnellen Code schreiben, der immer noch elegant und schön ist. Aber wenn Sie dieses Zeug falsch verstehen, läuft Ihr Programm tausende Male langsamer , und es ist wirklich nicht offensichtlich, warum dies geschieht.
Zum Beispiel habe ich ein triviales kleines Programm geschrieben, um zu zählen, wie oft jedes Byte in einer Datei erscheint. Bei einer 25-KB-Eingabedatei dauerte die Ausführung des Programms 20 Minuten und es wurden 6 Gigabyte RAM verschluckt ! Das ist absurd!! Aber dann wurde mir klar, was das Problem war, ich fügte ein einzelnes Knallmuster hinzu und die Laufzeit sank auf 0,02 Sekunden .
Hier geht Haskell unerwartet langsam. Und es dauert sicher eine Weile, bis man sich daran gewöhnt hat. Mit der Zeit wird es jedoch einfacher, sehr schnellen Code zu schreiben.
Was macht Haskell so schnell? Reinheit. Statische Typen. Faulheit. Vor allem aber muss der Compiler die Implementierung radikal ändern, ohne die Erwartungen Ihres Codes zu verletzen.
Aber ich denke, das ist nur meine Meinung ...