Ich kenne nicht die richtige Terminologie, um diese Frage zu stellen, deshalb werde ich sie stattdessen mit vielen Worten beschreiben.
Hintergrund , nur damit wir uns auf derselben Seite befinden: Programme enthalten häufig Caches - ein Kompromiss zwischen Zeit und Speicher. Ein häufiger Fehler eines Programmierers besteht darin, zu vergessen, einen zwischengespeicherten Wert zu aktualisieren, nachdem eine seiner vorgelagerten Quellen / Präzedenzfälle geändert wurde. Das Datenfluss- oder FRP-Programmierparadigma ist jedoch immun gegen solche Fehler. Wenn wir eine Reihe von reinen Funktionen haben und diese in einem gerichteten Abhängigkeitsgraphen miteinander verbinden, können die Ausgabewerte der Knoten zwischengespeichert und wiederverwendet werden, bis sich die Eingaben der Funktion ändern. Diese Systemarchitektur wird im Artikel Caching in datenflussbasierten Umgebungen beschrieben und ist in einer zwingenden Sprache mehr oder weniger analog zur Memoisierung.
Problem : Wenn sich eine der Eingaben in eine Funktion ändert, müssen wir die Funktion immer noch als Ganzes ausführen, ihre zwischengespeicherte Ausgabe wegwerfen und von Grund auf neu berechnen. In vielen Fällen erscheint mir dies verschwenderisch. Stellen Sie sich ein einfaches Beispiel vor, das eine "Top 5 Whatever" -Liste generiert. Die Eingabedaten sind eine unsortierte Liste von allem. Es wird als Eingabe an eine Funktion übergeben, die eine sortierte Liste ausgibt. Was wiederum in eine Funktion eingegeben wird, die nur die ersten 5 Elemente akzeptiert. Im Pseudocode:
input = [5, 20, 7, 2, 4, 9, 6, 13, 1, 45]
intermediate = sort(input)
final_output = substring(intermediate, 0, 5)
Die Komplexität der Sortierfunktion ist O (N log N). Beachten Sie jedoch, dass dieser Ablauf in einer Anwendung verwendet wird, in der sich die Eingabe jeweils nur geringfügig ändert, indem 1 Element hinzugefügt wird. Anstatt jedes Mal von Grund auf neu zu sortieren, wäre es schneller, tatsächlich O (N), eine Funktion zu verwenden, die die alte zwischengespeicherte sortierte Liste aktualisiert, indem das neue Element an der richtigen Position eingefügt wird. Dies ist nur ein Beispiel - viele "von Grund auf neu" -Funktionen haben solche Gegenstücke für "inkrementelle Aktualisierungen". Außerdem wird das neu hinzugefügte Element möglicherweise nicht einmal im final_output angezeigt, da es nach der 5. Position liegt.
Meine Intuition legt nahe, dass es möglich sein könnte, einem Datenflusssystem solche "inkrementellen Aktualisierungs" -Funktionen neben den vorhandenen "von Grund auf neu" -Funktionen hinzuzufügen. Natürlich muss die Neuberechnung von Grund auf immer das gleiche Ergebnis liefern wie eine Reihe inkrementeller Aktualisierungen. Das System sollte die Eigenschaft haben , dass , wenn jeder der einzelnen primitiven FromScratch-Inkremental - Paare immer das gleiche Ergebnis geben, dann die größeren Verbundfunktionen von ihnen gebaut sollte auch automatisch das gleiche Ergebnis.
Frage : Ist es möglich, ein System / eine Architektur / ein Paradigma / einen Meta-Algorithmus zu haben, der / der sowohl FromScratch-Funktionen als auch deren inkrementelle Gegenstücke unterstützt, aus Effizienzgründen zusammenarbeitet und zu großen Flows zusammengesetzt ist? Wenn nicht, warum? Wenn jemand dieses Paradigma bereits untersucht und veröffentlicht hat, wie heißt es und kann ich eine kurze Zusammenfassung seiner Funktionsweise erhalten?
Antworten:
Dieses Gebiet wurde viele Male erfunden und trägt viele Namen, wie zum Beispiel:
(Und möglicherweise mehr.) Diese sind nicht gleich, aber verwandt.
Paraphrasierung von Cai et al. (1): Es gibt zwei grundlegende Möglichkeiten, Online-Algorithmen generisch zu implementieren (dh ohne Bezug auf ein bestimmtes algorithmisches Problem):
Statische Inkrementalisierung. Statische Ansätze analysieren ein Programm zur Kompilierungszeit und erstellen eine inkrementelle Version, die die Ausgabe des ursprünglichen Programms entsprechend den sich ändernden Eingaben effizient aktualisiert. Statische Ansätze können effizienter sein als dynamische Ansätze, da zur Laufzeit keine Buchhaltung erforderlich ist. Außerdem können die berechneten inkrementellen Versionen häufig mithilfe von Standard-Compilertechniken wie konstantem Falten oder Inlining optimiert werden. Dies ist der in (1) untersuchte Ansatz.
Dynamische Inkrementalisierung. Dynamische Ansätze erstellen dynamische Abhängigkeitsdiagramme, während das Programm ausgeführt wird, und übertragen Änderungen entlang dieser Diagramme. Der bekannteste Ansatz ist die selbstanpassende Berechnung von Acar. Die Schlüsselidee ist einfach: Programme werden auf der ursprünglichen Eingabe in einer erweiterten Laufzeitumgebung ausgeführt, die die Abhängigkeiten zwischen Werten in einem dynamischen Abhängigkeitsdiagramm verfolgt. Zwischenergebnisse werden zwischengespeichert. (Wie Sie sich vorstellen können, verbraucht dies in der Regel viel Speicher, und in diesem Bereich wird viel darüber geforscht, wie die Speichernutzung begrenzt werden kann.) Später werden Änderungen an der Eingabe durch Abhängigkeitsdiagramme von geänderten Eingaben zu Ergebnissen übertragen, wobei sowohl Zwischen- als auch Zwischendaten aktualisiert werden endgültige Ergebnisse; Diese Verarbeitung ist oft effizienter als eine Neuberechnung. Das Erstellen dynamischer Abhängigkeitsgraphen verursacht jedoch zur Laufzeit einen hohen Overhead mit konstantem Faktor, der in den angegebenen Experimenten zwischen 2 und 30 liegt.
Außerdem kann man immer versuchen, eine Online-Version eines bestimmten Algorithmus von Hand zu erstellen. Das kann schwierig sein.
(1) Y. Cai, PG Giarrusso, T. Rendel, K. Ostermann, Eine Theorie der Änderungen für Sprachen höherer Ordnung: Inkrementalisierung von λ-Kalkülen durch statische Differenzierung .
quelle
Sie suchen wahrscheinlich nach adaptiver Programmierung . Siehe auch Umut Acars Doktorarbeit . Ich bin mit diesem Arbeitsbereich nicht auf dem neuesten Stand, aber es sollte Ihnen den Einstieg erleichtern, Sie können Referenzen suchen.
quelle