Haskell: Lazy bewertete Ergebnisse teilweise fallen lassen

8

Ich habe einen sehr großen Entscheidungsbaum. Es wird wie folgt verwendet:

%Vor%

Dieser Entscheidungsbaum ist viel zu groß, um in den Speicher zu passen. Aber dank der Lazy Evaluation wird es nur teilweise ausgewertet.

Das Problem ist, dass es Szenarien gibt, in denen alle möglichen Entscheidungen versucht werden, wodurch der gesamte Baum evaluiert wird. Dies wird nicht beendet, sollte aber auch keinen Speicherüberlauf verursachen. Wenn dieser Prozess abgebrochen wird, nimmt die Speichernutzung nicht ab, da ein großer Teilbaum noch ausgewertet wird.

Eine Lösung wäre es, den Baum jedes Mal neu zu bewerten, wenn makeDecision aufgerufen wird, aber dies würde die Vorteile von Caching-Entscheidungen verlieren und makeDecision erheblich verlangsamen.

Ich würde gerne einen Mittelweg gehen. Insbesondere ist es in meiner Anwendung sehr üblich, aufeinanderfolgende Entscheidungen mit einem gemeinsamen Pfadpräfix im Baum zu treffen. Also möchte ich den letzten benutzten Pfad zwischenspeichern, aber die anderen fallenlassen, was dazu führt, dass sie das nächste Mal, wenn sie benutzt werden, neu bewerten. Wie kann ich das in Haskell machen?

    
ipsec 18.01.2013, 08:55
quelle

1 Antwort

6

In reinem Haskell ist das nicht möglich, siehe Frage Can ein Thunk dupliziert werden, um die Speicherleistung zu verbessern? (wie von @shang hervorgehoben). Sie können dies jedoch mit IO tun.

Wir beginnen mit dem Modul heade und listen nur den Typ und die Funktionen auf, die dieses Modul (welches unsafePerformIO verwendet) sicher machen sollte. Es ist auch möglich, dies ohne unsafePerformIO zu tun, aber das würde bedeuten, dass der Benutzer mehr von seinem Code in IO behalten muss.

%Vor%

Wir beginnen damit, einen Datentyp zu definieren, der einen Wert so speichert, dass die gemeinsame Nutzung verhindert wird, indem wir die Funktion und das Argument voneinander fernhalten und die Funktion nur anwenden, wenn wir den Wert haben wollen. Beachten Sie, dass der von unsharedValue zurückgegebene Wert gemeinsam verwendet werden kann, jedoch nicht mit dem Rückgabewert anderer Aufrufe (vorausgesetzt, die Funktion tut etwas nicht-triviales):

%Vor%

Nun definieren wir unseren Datentyp für rücksetzbare Berechnungen. Wir müssen die Berechnung und den aktuellen Wert speichern. Letzteres wird in IORef gespeichert, da wir es zurücksetzen möchten.

%Vor%

Um einen Wert in eine ReEval Box zu schreiben, brauchen wir eine Funktion und ein Argument. Warum nicht nur a -> ReEval a ? Denn dann wäre es nicht möglich zu verhindern, dass der Parameter geteilt wird.

%Vor%

Das Lesen ist einfach: Holen Sie sich einfach den Wert von IORef . Diese Verwendung von unsafePerformIO ist sicher, da wir immer den Wert von unsharedValue c erhalten, obwohl es eine andere "Kopie" davon ist.

%Vor%

Und schließlich das Zurücksetzen. Ich habe es in der IO-Monade gelassen, nicht weil es weniger sicher wäre als die andere Funktion, die in unsafePerformIO eingepackt wird, sondern weil dies der einfachste Weg ist, dem Benutzer die Kontrolle über zu geben, wenn der das Zurücksetzen geschieht tatsächlich. Sie möchten nicht riskieren, dass all Ihre Aufrufe von resetReEval verzögert verzögert werden, bis der Speicher aufgebraucht ist oder sogar wegoptimiert ist, da kein Rückgabewert zu verwenden ist.

%Vor%

Dies ist das Ende des Moduls. Hier ist ein Beispielcode:

%Vor%

Und hier können Sie sehen, dass es tut, was erwartet:

%Vor%     
Joachim Breitner 18.01.2013, 16:32
quelle