λ-Kalkül: Was ist die effizienteste Darstellung von Funktionen im Speicher?


9

Ich möchte die Leistung von funktionscodierten (Church's / Scott's) mit klassisch codierten (Assembler / C) Datenstrukturen vergleichen.

Aber bevor ich das tue, muss ich wissen, wie effizient die Funktionsrepräsentation im Speicher ist / sein kann. Die Funktion kann natürlich teilweise angewendet werden (auch bekannt als Closure).

Ich interessiere mich sowohl für den aktuellen Codierungsalgorithmus, den beliebte Funktionssprachen (Haskell, ML) verwenden, als auch für die effizienteste, die erreicht werden kann.


Bonuspunkt: Gibt es eine solche Codierung , dass die Karten ganzen Zahlen auf native ganze Zahlen kodiert Funktion ( short, intetc in C). Ist es überhaupt möglich?


Ich schätze Effizienz basierend auf Leistung. Mit anderen Worten, je effizienter die Codierung ist, desto weniger beeinflusst sie die Leistung der Berechnung mit funktionalen Datenstrukturen.


Alle meine Google-Versuche sind fehlgeschlagen. Vielleicht kenne ich nicht die richtigen Keywords.
Ford O.

Können Sie die Frage bearbeiten, um zu verdeutlichen, was Sie unter "effizient" verstehen? Effizient für was? Wenn Sie nach einer effizienten Datenstruktur fragen, müssen Sie angeben, welche Vorgänge Sie für die Datenstruktur ausführen möchten, da dies die Auswahl der Datenstruktur beeinflusst. Oder meinen Sie damit, dass die Codierung so platzsparend wie möglich ist?
DW

1
Das ist ziemlich breit. Es gibt viele abstrakte Maschinen für die Lambda-Berechnung, die darauf abzielen, sie effizient auszuführen (siehe z. B. SECD, CAM, Krivines, STG). Darüber hinaus müssen Sie Church / Scott-codierte Daten berücksichtigen, was zu weiteren Problemen führt. Beispielsweise muss in kirchenkodierten Listen die Schwanzoperation O (n) anstelle von O (1) sein. Ich glaube, ich habe irgendwo gelesen, dass das Vorhandensein einer Codierung für Listen in System F mit O (1) -Kopf- und Schwanzoperationen immer noch ein offenes Problem war.
Chi

@ DW Ich spreche über Leistung / Overhead. Zum Beispiel sollte bei einer effizienten Codierung die Zuordnung über die Liste der Kirche und die Liste von Haskell dieselbe Zeit dauern.
Ford O.

Leistung für welche Operation (en)? Was möchten Sie mit den Funktionen tun? Möchten Sie diese Funktionen anhand eines bestimmten Werts bewerten? Einmal oder die gleiche Funktion bei vielen Werten auswerten? Mach noch etwas mit ihnen? Fragen Sie sich nur, wie eine Funktion (in einer funktionalen Sprache geschrieben) kompiliert werden soll, damit sie so effizient wie möglich ausgeführt werden kann?
DW

Antworten:


11

Die Sache ist, es gibt wirklich nicht viel Spielraum in Bezug auf die Funktionscodierung. Hier sind die Hauptoptionen:

  • Umschreiben von Begriffen: Sie speichern Funktionen als abstrakte Syntaxbäume (oder deren Codierung). Wenn Sie eine Funktion aufrufen, durchlaufen Sie den Syntaxbaum manuell, um seine Parameter durch das Argument zu ersetzen. Dies ist einfach, aber zeitlich und räumlich äußerst ineffizient .

  • Abschlüsse: Sie haben eine Möglichkeit, eine Funktion darzustellen, möglicherweise einen Syntaxbaum, wahrscheinlicher Maschinencode. Und in diesen Funktionen verweisen Sie in irgendeiner Weise auf Ihre Argumente. Es könnte ein Zeiger-Offset sein, es könnte eine Ganzzahl oder ein De Bruijn-Index sein, es könnte ein Name sein. Dann stellen Sie eine Funktion als Abschluss dar : die Funktion "Anweisungen" (Baum, Code usw.) gepaart mit einer Datenstruktur, die alle freien Variablen der Funktion enthält. Wenn eine Funktion tatsächlich angewendet wird, weiß sie irgendwie, wie die freien Variablen in ihrer Datenstruktur mithilfe von Umgebungen, Zeigerarithmetik usw. nachgeschlagen werden.

Ich bin sicher, dass es andere Optionen gibt, aber dies sind die grundlegenden, und ich vermute, dass fast jede andere Option eine Variante oder Optimierung der grundlegenden Verschlussstruktur sein wird.

In Bezug auf die Leistung sind Schließungen also fast überall besser als das Umschreiben von Begriffen. Welche der Variationen ist besser? Das hängt stark von Ihrer Sprache und Architektur ab, aber ich vermute, dass der "Maschinencode mit einer Struktur, die freie Variablen enthält", am effizientesten ist. Es hat alles, was die Funktion benötigt (Anweisungen und Werte) und nichts weiter, und das Aufrufen führt nicht zu großen Durchläufen.

Ich interessiere mich sowohl für den aktuellen Codierungsalgorithmus, den beliebte Funktionssprachen (Haskell, ML) verwenden

Ich bin kein Experte, aber ich bin zu 99% der Meinung, dass die meisten ML-Geschmacksrichtungen eine Variation der von mir beschriebenen Verschlüsse verwenden, obwohl einige Optimierungen wahrscheinlich sind. Sehen Sie dies für eine (möglicherweise veraltete) Perspektive.

Haskell macht etwas komplizierter wegen der verzögerten Auswertung: Es verwendet Spineless Tagless Graph Rewriting .

und auch in der effizientesten, die erreicht werden kann.

Was ist am effizientesten? Es gibt keine Implementierung, die für alle Eingaben am effizientesten ist. Sie erhalten also Implementierungen, die im Durchschnitt effizient sind, aber jede zeichnet sich in unterschiedlichen Szenarien aus. Es gibt also keine eindeutige Rangfolge der meisten oder am wenigsten effizienten.

Hier gibt es keine Magie. Um eine Funktion zu speichern, Sie müssen ihre freien Werte speichern irgendwie, sonst sind Sie kodieren weniger Informationen als die Funktion selbst hat. Vielleicht können Sie einige der freien Werte durch teilweise Auswertung optimieren, aber das ist riskant für die Leistung, und Sie müssen vorsichtig sein, um sicherzustellen, dass dies immer anhält.

Und vielleicht können Sie eine Art Komprimierung oder einen cleveren Algorithmus verwenden, um Raumeffizienz zu erzielen. Aber dann tauschen Sie entweder Zeit gegen Raum oder Sie befinden sich in einer Situation, in der Sie für einige Fälle optimiert und für andere langsamer geworden sind.

Sie können für den gemeinsamen Fall optimieren, aber der gemeinsame Fall , was sind auf der Sprache ändern, Einsatzgebiet, usw. Die Art des Code, der für ein Videospiel schnell ist (Anzahl, enge Schleifen mit großen Eingängen Knirschen) sind wahrscheinlich anders als Was ist schnell für einen Compiler (Baumdurchläufe, Arbeitslisten usw.)?

Bonuspunkt: Gibt es eine solche Codierung, die funktionscodierte Ganzzahlen nativen Ganzzahlen (kurz, int usw. in C) zuordnet? Ist es überhaupt möglich?

Nein das ist nicht möglich. Das Problem ist, dass Sie mit dem Lambda-Kalkül keine Begriffe überprüfen können. Wenn eine Funktion ein Argument mit demselben Typ wie eine Kirchenzahl verwendet, muss sie es aufrufen können, ohne die genaue Definition dieser Zahl zu untersuchen. Das ist die Sache mit den Kodierungen der Kirche: Das einzige, was Sie damit machen können, ist, sie anzurufen, und Sie können alles Nützliche damit simulieren, aber nicht ohne Kosten.

Noch wichtiger ist, dass die Ganzzahlen jede mögliche binäre Codierung belegen. Wenn also Lambdas als ihre ganzen Zahlen dargestellt würden, hätten Sie keine Möglichkeit, nicht kirchliche Lambdas darzustellen! Oder Sie würden eine Flagge einführen, um anzuzeigen, ob ein Lambda eine Ziffer ist oder nicht, aber dann ist wahrscheinlich jede gewünschte Effizienz aus dem Fenster verschwunden.

EDIT: Seit ich dies schreibe, ist mir eine dritte Option für die Implementierung von Funktionen höherer Ordnung bekannt geworden: die Defunktionalisierung . Hier wird jeder Funktionsaufruf zu einer großen switchAussage, je nachdem, welche Lambda-Abstraktion als Funktion angegeben wurde. Der Nachteil hierbei ist, dass es sich um eine vollständige Programmtransformation handelt: Sie können Teile nicht separat kompilieren und dann auf diese Weise miteinander verknüpfen, da Sie den vollständigen Satz von Lambda-Abstraktionen im Voraus benötigen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.