Welche Algorithmen können mit einer Gesamtfunktionssprache mit datenparallelen Operatoren ausgedrückt werden?

11

Stellen Sie sich eine funktionale Programmiersprache vor, deren einzige Datentypen numerische Skalare und beliebige Verschachtelungen von Arrays sind. Der Sprache fehlen alle Mittel zur unbegrenzten Iteration, daher sind die folgenden nicht zulässig:

  • explizite Schleifen (ohne Nebenwirkungen sowieso nicht viel zu gebrauchen)
  • Rekursion
  • beliebige erstklassige Funktionen (kein y-Kombinator)

Die Sprache hat jedoch:

  • Funktionen der obersten Ebene
  • lexikalisch angelegt lassen Bindungen
  • Verzweigungssteuerungsfluss
  • gemeinsame skalare mathematische und logische Funktionen
  • Ein einfacher Array-Konstruktor wie fill (n, x), der ein n-Element-Array mit identischen Werten x erstellt
  • Am wichtigsten ist: eine eingeschränkte Gruppe von Operatoren höherer Ordnung, die eine parallel strukturierte Iteration durchführen (z. B. Map, Reduce, Scan, All-Pair).

Genauer gesagt zu den Datenparalleloperatoren:

  • y = Karte (f, x) => y [i] = f [i]
  • y = reduziere (f, a, x) => y = f (a, f (y [p [0]], f (y [p [1]], ...))) für eine Permutation p
  • y = Scan (f, a, x) => y [i] = reduzieren (f, a, y [0 ... i-1])
  • y = alle Paare (f, x, y) => y [i, j] = f (x [i], y [j])

Wir könnten auch andere Operatoren haben, aber um sie zu qualifizieren, sollten sie eine Polynomlaufzeit haben, unter einem vernünftigen Modell der datenparallelen Berechnung implementierbar sein und höchstens Polynomraum verwenden.

Es gibt offensichtlich einige Konstrukte, die in dieser Sprache nicht ausgedrückt werden können, wie zum Beispiel:

while f(x) > tol:
    x <- update(x)   

Was können wir in diesem System ausdrücken? Beschränken wir uns nur auf Suchprobleme in FP? Können wir alle polynomiellen Zeitalgorithmen erfassen? Gibt es auch eine minimale Anzahl von Operatoren für diese Klasse?

Alex Rubinsteyn
quelle

Antworten:

7

Vielleicht möchten Sie sich die alte Programmiersprache NESL ansehen, die diese Ideen ernst nahm. Die Sprache basiert auf Operationen an Sammlungen, im Wesentlichen Listen und Listen von Listen usw., aber ich denke, Bäume und Grafiken wurden auch über knifflige Codierungen berücksichtigt. Einige der in diesem Projekt (Mitte der neunziger Jahre) durchgeführten Untersuchungen könnten zur Beantwortung Ihrer Frage beitragen. Die Doktorarbeit (als Buch erhältlich) Guy E. Blelloch. Vektormodelle für datenparalleles Rechnen. MIT Press, 1990 kann einige Antworten geben. Es ist einige Zeit her, seit ich es angeschaut habe.

Die Arbeit am Bird-Meertens-Formalismus (BMF) fällt ebenso in diese Kategorie wie die Sprache Charity . Auf der Wikipedia-Seite von Charity heißt es, dass die Sprache nicht vollständig ist, sondern Ackermanns Funktion ausdrücken kann, was bedeutet, dass sie mehr als primitiv rekursiv ist. Sowohl BMF als auch Charity beinhalten Operatoren wie Falten und Scans (Katamorphismen, Anamorphismen usw.) und haben ihre Wurzeln in der Kategorietheorie.

Ich kurz, ungenaue Antwort ist, dass Sie ziemlich viel ausdrücken können.

Dave Clarke
quelle
1
NESL ist keine Gesamtsprache.
Per Vognsen
Ich war mir NESL eine Weile oberflächlich bewusst, las aber zum ersten Mal einen von Blellochs Artikeln im Detail. Danke für den Tipp. NESL ist der oben beschriebenen Sprache ziemlich ähnlich, außer dass es, wie Per Vognsen bemerkte, eine Rekursion ermöglicht.
Alex Rubinsteyn
Ich interessiere mich auch für Blellochs Auswahl primitiver Operatoren: map, dist (ich glaube dasselbe wie das, was ich 'fill' genannt habe), length, array-read, array-write, scan, partition, flatten. Sind die Grundelemente von NESL "vollständig" oder gibt es eine andere Operation mit einer datenparallelen Implementierung, die mit diesen nicht effizient ausgedrückt werden kann?
Alex Rubinsteyn
2
Entfernen Sie die Rekursion, dann haben Sie eine ziemlich ausdrucksstarke Sprache, besonders wenn Sie Falten und so weiter berücksichtigen. Ein Blick auf BMF und die darauf folgenden Arbeiten könnte dann interessanter sein. Es tut mir leid, aber ich bin in diesem Bereich nicht auf dem neuesten Stand.
Dave Clarke
7

Meine andere Antwort wies auf Mängel in der aktuellen Sprache hin. In dieser Antwort werde ich detaillierter auf die Probleme mit der Koexistenz von Falten und Entfaltungen in einer Gesamtsprache eingehen. Dann werde ich eine Lösung vorschlagen und zeigen, dass alle Probleme in P (und in der Tat viele weitere) in dieser erweiterten Sprache gelöst werden können.

Das Falten in einer Gesamtsprache verbraucht Listen:

fold :: (a -> b -> b) -> b -> List a -> b

Das Entfalten in einer Gesamtsprache erzeugt Streams, die möglicherweise unbegrenzt sind:

unfold :: (b -> Maybe (a, b)) -> b -> Stream a

Leider leben Listen und Streams in verschiedenen Welten, sodass diese Operatoren nicht zusammengestellt werden können. Wir brauchen eine teilweise Korrespondenz:

stream :: List a -> Stream a
list :: Int -> Stream a -> List a

Der Stream-Operator bettet eine Liste in einen begrenzten Stream ein. Die Listenfunktion extrahiert die ersten n Elemente (oder weniger, wenn der Stream früher beendet wird) in eine Liste. Wir haben also die folgende Gleichung:

for all xs :: List a, xs == list (length xs) (stream xs)

Als Effizienzoptimierung können wir Streams als Zwischendatenstruktur vollständig ausschneiden:

unfoldList :: Int -> (b -> Maybe (a, b)) -> b -> List a

Ich werde nun einen Beweis skizzieren, dass dies (mit den anderen Operatoren, die bereits in der ursprünglichen Frage impliziert sind) die Simulation eines beliebigen Polynom-Zeit-Algorithmus ermöglicht.

Per Definition ist eine Sprache L in P, wenn es eine Turing-Maschine M und ein Polynom p gibt, so dass die Zugehörigkeit von x in L durch Ausführen von M höchstens p (n) Iterationen mit n = | x | entschieden werden kann. Durch ein Standardargument kann der Zustand des Bandes der Maschine in Iteration k mit einer Liste von höchstens 2k + 1 codiert werden, obwohl das Band der Maschine unendlich ist.

Die Idee ist nun, Ms Übergang als Funktion von Listen zu Listen darzustellen. Die Ausführung der Maschine erfolgt durch Entfalten des Ausgangszustandes mit der Übergangsfunktion. Dies erzeugt einen Strom von Listen. Die Annahme, dass L in P ist, bedeutet, dass wir nicht weiter als p (n) Elemente in den Strom schauen müssen. So können wir die Entfaltung mit komponieren list p(n), um eine endliche Liste zu erhalten. Schließlich falten wir es um, um zu prüfen, ob die Antwort auf das Entscheidungsproblem Ja oder Nein war.

Allgemeiner zeigt dies, dass wir einen Algorithmus simulieren können, dessen Abschlusszeit durch eine in der Sprache berechenbare Funktion begrenzt werden kann. Dies legt auch nahe, warum so etwas wie Ackermanns Funktion nicht simuliert werden kann: Es ist eine eigene Bindung, daher gibt es ein Henne-Ei-Problem.

Per Vognsen
quelle
4

Es ist eine sehr gimped Sprache. Versuchen Sie, die Fakultätsfunktion zu programmieren:

fact 0 = 1
fact n = n * fact (n-1)

Das Problem ist, dass Ihre Sprache nur Falten hat, aber keine Falten. Die natürliche Art, Fakultät auszudrücken, besteht darin, eine Entfaltung von n in die Liste [1, 2, ..., n] mit der Faltung zu setzen, die es beim Multiplizieren abreißt.

Interessieren Sie sich wirklich für diese spezielle Sprache oder für die gesamte funktionale Programmierung im Allgemeinen? Es ist offensichtlich, dass Ihre Sprache höchstens Polynomzeitalgorithmen ausdrücken kann. System F (polymorpher Lambda-Kalkül) kann Monster wie Ackermanns Funktion mühelos ausdrücken. Selbst wenn Sie sich für Polynom-Zeit-Algorithmen interessieren, benötigen Sie häufig den Super-Polynom-Spielraum, um diese auf natürliche Weise auszudrücken.

Bearbeiten: Wie Dave betont, ist NESL eine der wegweisenden funktionalen datenparallelen Programmiersprachen, aber es ist alles andere als vollständig (es wird nicht einmal versucht). Die APL-Familie hatte eine parallele Evolutionsspur und eine gesamte algebraische Teilmenge (vermeiden Sie die Festkommaoperatoren). Wenn der Schwerpunkt Ihrer Frage auf der Gesamtheit liegt, hat David Turner einige gute Artikel in diesem Bereich verfasst, die sich jedoch nicht speziell mit datenparalleler Programmierung befassen.

Per Vognsen
quelle
Entschuldigung, das Fehlen von Entfaltungsoperatoren ist meinerseits ein Versehen ... Ich wollte einen hinzufügen, habe aber vergessen, ihn in den Beitrag aufzunehmen. Diese spezielle Sprache interessiert mich nicht unbedingt, sondern die Ausdruckskraft und die Grenzen der datenparallelen Berechnung.
Alex Rubinsteyn
Das Entfalten ist natürlich ein Stream-Wert, kein Array-Wert, was ein Grundproblem bei der Corecursion in absolut strengen Sprachen darstellt.
Per Vognsen