Wie funktioniert die differenzielle Ausführung?


82

Ich habe einige Erwähnungen davon auf Stack Overflow gesehen, aber das Starren auf Wikipedia (die entsprechende Seite wurde inzwischen gelöscht) und auf eine MFC-Demo für dynamische Dialoge hat mich nicht aufgeklärt. Kann das bitte jemand erklären? Ein grundlegend anderes Konzept zu lernen, klingt gut.


Basierend auf den Antworten: Ich denke, ich bekomme ein besseres Gefühl dafür. Ich glaube, ich habe mir den Quellcode beim ersten Mal nicht genau genug angesehen. Ich habe zu diesem Zeitpunkt gemischte Gefühle in Bezug auf die unterschiedliche Ausführung. Einerseits kann es bestimmte Aufgaben erheblich erleichtern. Auf der anderen Seite ist es nicht einfach, es zum Laufen zu bringen (dh es in der Sprache Ihrer Wahl einzurichten) (ich bin sicher, es wäre, wenn ich es besser verstehen würde) ... obwohl ich die Toolbox dafür schätze müssen nur einmal gemacht und dann nach Bedarf erweitert werden. Ich denke, um es wirklich zu verstehen, muss ich wahrscheinlich versuchen, es in einer anderen Sprache zu implementieren.


3
Vielen Dank für Ihr Interesse Brian. Für mich ist es interessant, dass etwas Einfaches enttäuschend erscheint. Für mich sind die schönsten Dinge einfach. Pass auf.
Mike Dunlavey

1
Ich glaube, ich vermisse etwas Wichtiges. Im Moment denke ich: "Das ist einfach." Wenn ich es wirklich verstehen würde, würde ich denken: "Das ist einfach. Und wirklich erstaunlich und nützlich."
Brian

6
... Ich sehe immer noch Leute, die MVC präsentieren, als wäre es das Beste, und ich denke, ich würde lieber in den Ruhestand gehen, als das noch einmal tun zu müssen.
Mike Dunlavey

1
... für "Rückgängig" serialisieren / deserialisieren Sie die Daten und drehen eine Datei aus, die das XOR der beiden ist, die meistens Null ist und so leicht komprimiert werden kann. Verwenden Sie dies, um die vorherigen Daten wiederherzustellen. Verallgemeinern Sie nun auf eine beliebige Datenstruktur.
Mike Dunlavey

1
Sie möchten Ihre Arbeitslast nicht erhöhen, @MikeDunlavey, aber wenn Sie sie verpasst haben, ist Source Forge mit fragwürdigen Geschäftspraktiken in Ungnade gefallen. Auf Github.com hängen heutzutage die coolen Kids. Sie haben einen wirklich schönen Windows-Client für W7 bei desktop.github.com
Prof. Falken

Antworten:


93

Gee, Brian, ich wünschte ich hätte deine Frage früher gesehen. Da es so ziemlich meine "Erfindung" ist (zum Guten oder Schlechten), kann ich vielleicht helfen.

Eingefügt: Die kürzest mögliche Erklärung, die ich machen kann, ist, dass wenn die normale Ausführung wie das Werfen eines Balls in die Luft und das Fangen ist, die differenzielle Ausführung dem Jonglieren gleicht.

Die Erklärung von @ windfinder unterscheidet sich von meiner, und das ist in Ordnung. Diese Technik ist nicht einfach, den Kopf herumzureißen, und ich habe ungefähr 20 Jahre (ab und zu) gebraucht, um Erklärungen zu finden, die funktionieren. Lassen Sie es mich hier noch einmal versuchen:

  • Was ist es?

Wir alle verstehen die einfache Idee, dass ein Computer ein Programm durchläuft, bedingte Verzweigungen basierend auf den Eingabedaten nimmt und Dinge tut. (Angenommen, es handelt sich nur um einfachen strukturierten Code ohne Rückgabe und ohne Rückgabe.) Dieser Code enthält Folgen von Anweisungen, strukturierte Grundbedingungen, einfache Schleifen und Unterprogrammaufrufe. (Vergessen Sie vorerst Funktionen, die Werte zurückgeben.)

Stellen Sie sich nun zwei Computer vor, die denselben Code im Gleichschritt miteinander ausführen und Notizen vergleichen können. Computer 1 wird mit Eingabedaten A und Computer 2 mit Eingabedaten B ausgeführt. Sie werden Schritt für Schritt nebeneinander ausgeführt. Wenn sie zu einer bedingten Aussage wie IF (Test) .... ENDIF kommen und wenn sie unterschiedliche Meinungen darüber haben, ob der Test wahr ist, dann springt derjenige, der den Test sagt, wenn er falsch ist, zum ENDIF und wartet herum seine Schwester aufzuholen. (Aus diesem Grund ist der Code strukturiert, sodass wir wissen, dass die Schwester irgendwann zum ENDIF gelangen wird.)

Da die beiden Computer miteinander kommunizieren können, können sie Notizen vergleichen und detailliert erklären, wie sich die beiden Eingabedatensätze und Ausführungsverläufe unterscheiden.

Bei der Differentialausführung (DE) wird dies natürlich mit einem Computer durchgeführt, wobei zwei simuliert werden.

Angenommen, Sie haben nur einen Satz von Eingabedaten, möchten aber sehen, wie sich dieser von Zeitpunkt 1 auf Zeitpunkt 2 geändert hat. Angenommen, das von Ihnen ausgeführte Programm ist ein Serializer / Deserializer. Während der Ausführung serialisieren (schreiben) Sie die aktuellen Daten aus und deserialisieren (lesen) die vergangenen Daten (die beim letzten Mal geschrieben wurden). Jetzt können Sie leicht erkennen, welche Unterschiede zwischen dem letzten Mal und dem letzten Mal bestehen.

Die Datei, in die Sie schreiben, und die alte Datei, aus der Sie lesen, bilden zusammen eine Warteschlange oder ein FIFO (First-In-First-Out), aber das ist kein sehr tiefgreifendes Konzept.

  • Wozu ist es gut?

Mir kam der Gedanke, als ich an einem Grafikprojekt arbeitete, bei dem der Benutzer kleine Routinen für Anzeigeprozessoren erstellen konnte, die als "Symbole" bezeichnet wurden und zu größeren Routinen zusammengesetzt werden konnten, um beispielsweise Diagramme von Rohren, Tanks, Ventilen usw. zu malen. Wir wollten, dass die Diagramme "dynamisch" sind, da sie sich schrittweise aktualisieren können, ohne das gesamte Diagramm neu zeichnen zu müssen. (Die Hardware war nach heutigen Maßstäben langsam.) Ich erkannte, dass (zum Beispiel) eine Routine zum Zeichnen eines Balkens eines Balkendiagramms sich an seine alte Höhe erinnern und sich nur schrittweise aktualisieren konnte.

Das klingt nach OOP, nicht wahr? Anstatt jedoch ein "Objekt" zu "machen", könnte ich die Vorhersagbarkeit der Ausführungssequenz der Diagrammprozedur nutzen. Ich könnte die Höhe des Balkens in einem sequentiellen Byte-Stream schreiben. Um das Image zu aktualisieren, könnte ich die Prozedur einfach in einem Modus ausführen, in dem die alten Parameter nacheinander gelesen werden, während die neuen Parameter geschrieben werden, um für den nächsten Aktualisierungsdurchlauf bereit zu sein.

Dies scheint dumm zu sein und scheint zu brechen, sobald die Prozedur eine Bedingung enthält, da dann der neue und der alte Stream nicht mehr synchron sind. Aber dann wurde mir klar, dass sie wieder synchronisiert werden könnten, wenn sie auch den Booleschen Wert des bedingten Tests serialisieren würden . Es hat eine Weile gedauert, mich zu überzeugen und dann zu beweisen, dass dies immer funktionieren würde, vorausgesetzt, eine einfache Regel (die "Löschmodus-Regel") wird befolgt.

Das Nettoergebnis ist, dass der Benutzer diese "dynamischen Symbole" entwerfen und zu größeren Diagrammen zusammenfügen kann, ohne sich jemals Gedanken darüber machen zu müssen, wie sie dynamisch aktualisiert werden, unabhängig davon, wie komplex oder strukturell variabel die Anzeige sein würde.

In jenen Tagen musste ich mir Sorgen um Interferenzen zwischen visuellen Objekten machen, damit das Löschen eines Objekts anderen nicht schadet. Jetzt verwende ich die Technik jedoch mit Windows-Steuerelementen und lasse Windows Rendering-Probleme beheben.

Was erreicht es also? Dies bedeutet, dass ich einen Dialog erstellen kann, indem ich eine Prozedur zum Zeichnen der Steuerelemente schreibe, und ich muss mich nicht darum kümmern, die Steuerelementobjekte tatsächlich zu speichern oder sie schrittweise zu aktualisieren oder sie erscheinen / verschwinden / verschieben zu lassen, wenn die Bedingungen dies erfordern. Das Ergebnis ist ein um eine Größenordnung viel kleinerer und einfacherer Dialogquellcode, und Dinge wie das dynamische Layout oder das Ändern der Anzahl von Steuerelementen oder das Vorhandensein von Arrays oder Gittern von Steuerelementen sind trivial. Darüber hinaus kann ein Steuerelement wie ein Bearbeitungsfeld trivial an die Anwendungsdaten gebunden sein, die es bearbeitet, und es ist immer nachweislich korrekt, und ich muss mich nie mit seinen Ereignissen befassen. Das Einfügen eines Bearbeitungsfelds für eine Anwendungszeichenfolgenvariable ist eine einzeilige Bearbeitung.

  • Warum ist es schwer zu verstehen?

Was ich am schwersten zu erklären fand, ist, dass man anders über Software denken muss. Programmierer sind so fest mit der Objekt-Aktions-Ansicht von Software verbunden, dass sie wissen möchten, was die Objekte sind, was die Klassen sind, wie sie die Anzeige "erstellen" und wie sie mit den Ereignissen umgehen, dass es eine Kirsche braucht Bombe, um sie heraus zu sprengen. Ich versuche zu vermitteln, dass es wirklich darauf ankommt, was Sie zu sagen haben.Stellen Sie sich vor, Sie erstellen eine domänenspezifische Sprache (DSL), in der Sie lediglich sagen müssen: "Ich möchte die Variable A hier, die Variable B dort und die Variable C dort unten bearbeiten", und sie würde sich auf magische Weise um Sie kümmern . In Win32 gibt es beispielsweise diese "Ressourcensprache" zum Definieren von Dialogen. Es ist ein perfektes DSL, außer dass es nicht weit genug geht. Es "lebt" nicht in der Hauptprozedursprache oder behandelt Ereignisse für Sie oder enthält Schleifen / Bedingungen / Unterprogramme. Aber es bedeutet gut, und Dynamic Dialogs versucht, den Job zu beenden.

Die andere Denkweise ist also: Um ein Programm zu schreiben, finden (oder erfinden) Sie zuerst ein geeignetes DSL und codieren so viel wie möglich von Ihrem Programm darin. Lassen Sie es sich mit allen Objekten und Aktionen befassen, die nur für die Implementierung existieren.

Wenn Sie die differenzielle Ausführung wirklich verstehen und verwenden möchten, gibt es einige knifflige Probleme, die Sie stören können. Ich habe es einmal in Lisp- Makros codiert , wo diese kniffligen Dinge für Sie erledigt werden könnten, aber in "normalen" Sprachen erfordert es etwas Programmiererdisziplin, um die Fallstricke zu vermeiden.

Tut mir leid, dass ich so langatmig bin. Wenn ich keinen Sinn ergeben habe, würde ich es begrüßen, wenn Sie darauf hinweisen und ich versuchen kann, es zu beheben.

Hinzugefügt:

In Java Swing gibt es ein Beispielprogramm namens TextInputDemo. Es ist ein statischer Dialog mit 270 Zeilen (ohne die Liste der 50 Zustände). In dynamischen Dialogen (in MFC) sind es ungefähr 60 Zeilen:

#define NSTATE (sizeof(states)/sizeof(states[0]))
CString sStreet;
CString sCity;
int iState;
CString sZip;
CString sWholeAddress;

void SetAddress(){
    CString sTemp = states[iState];
    int len = sTemp.GetLength();
    sWholeAddress.Format("%s\r\n%s %s %s", sStreet, sCity, sTemp.Mid(len-3, 2), sZip);
}

void ClearAddress(){
    sWholeAddress = sStreet = sCity = sZip = "";
}

void CDDDemoDlg::deContentsTextInputDemo(){
    int gy0 = P(gy);
    P(www = Width()*2/3);
    deStartHorizontal();
    deStatic(100, 20, "Street Address:");
    deEdit(www - 100, 20, &sStreet);
    deEndHorizontal(20);
    deStartHorizontal();
    deStatic(100, 20, "City:");
    deEdit(www - 100, 20, &sCity);
    deEndHorizontal(20);
    deStartHorizontal();
    deStatic(100, 20, "State:");
    deStatic(www - 100 - 20 - 20, 20, states[iState]);
    if (deButton(20, 20, "<")){
        iState = (iState+NSTATE - 1) % NSTATE;
        DD_THROW;
    }
    if (deButton(20, 20, ">")){
        iState = (iState+NSTATE + 1) % NSTATE;
        DD_THROW;
    }
    deEndHorizontal(20);
    deStartHorizontal();
    deStatic(100, 20, "Zip:");
    deEdit(www - 100, 20, &sZip);
    deEndHorizontal(20);
    deStartHorizontal();
    P(gx += 100);
    if (deButton((www-100)/2, 20, "Set Address")){
        SetAddress();
        DD_THROW;
    }
    if (deButton((www-100)/2, 20, "Clear Address")){
        ClearAddress();
        DD_THROW;
    }
    deEndHorizontal(20);
    P((gx = www, gy = gy0));
    deStatic(P(Width() - gx), 20*5, (sWholeAddress != "" ? sWholeAddress : "No address set."));
}

Hinzugefügt:

Hier ist ein Beispielcode zum Bearbeiten einer Reihe von Krankenhauspatienten in etwa 40 Codezeilen. Die Zeilen 1-6 definieren die "Datenbank". Die Zeilen 10-23 definieren den Gesamtinhalt der Benutzeroberfläche. Die Zeilen 30-48 definieren die Steuerelemente zum Bearbeiten einer einzelnen Patientenakte. Beachten Sie, dass die Form des Programms Ereignisse zeitlich fast nicht berücksichtigt, als ob die Anzeige nur einmal erstellt werden müsste. Wenn dann Themen hinzugefügt oder entfernt werden oder andere strukturelle Änderungen stattfinden, wird es einfach erneut ausgeführt, als würde es von Grund auf neu erstellt, mit der Ausnahme, dass DE stattdessen eine inkrementelle Aktualisierung bewirkt. Der Vorteil ist, dass Sie als Programmierer keine Aufmerksamkeit schenken oder Code schreiben müssen, um die inkrementellen Aktualisierungen der Benutzeroberfläche durchzuführen, und sie sind garantiert korrekt. Es scheint, dass diese erneute Ausführung ein Leistungsproblem darstellt, ist es aber nicht.

1  class Patient {public:
2    String name;
3    double age;
4    bool smoker; // smoker only relevant if age >= 50
5  };
6  vector< Patient* > patients;

10 void deContents(){ int i;
11   // First, have a label
12   deLabel(200, 20, “Patient name, age, smoker:”);
13   // For each patient, have a row of controls
14   FOR(i=0, i<patients.Count(), i++)
15     deEditOnePatient( P( patients[i] ) );
16   END
17   // Have a button to add a patient
18   if (deButton(50, 20, “Add”)){
19     // When the button is clicked add the patient
20     patients.Add(new Patient);
21     DD_THROW;
22   }
23 }

30 void deEditOnePatient(Patient* p){
31   // Determine field widths
32   int w = (Width()-50)/3;
33   // Controls are laid out horizontally
34   deStartHorizontal();
35     // Have a button to remove this patient
36     if (deButton(50, 20, “Remove”)){
37       patients.Remove(p);
37       DD_THROW;
39     }
40     // Edit fields for name and age
41     deEdit(w, 20, P(&p->name));
42     deEdit(w, 20, P(&p->age));
43     // If age >= 50 have a checkbox for smoker boolean
44     IF(p->age >= 50)
45       deCheckBox(w, 20, “Smoker?”, P(&p->smoker));
46     END
47   deEndHorizontal(20);
48 }

Hinzugefügt: Brian hat eine gute Frage gestellt, und ich dachte, die Antwort gehört in den Haupttext hier:

@ Mike: Mir ist nicht klar, was die Anweisung "if (deButton (50, 20," Add ")) {" tatsächlich tut. Was macht die deButton-Funktion? Verwenden Ihre FOR / END-Schleifen auch ein Makro oder etwas anderes? - Brian.

@Brian: Ja, die FOR / END- und IF-Anweisungen sind Makros. Das SourceForge-Projekt ist vollständig implementiert. deButton behält eine Tastensteuerung bei. Wenn eine Benutzereingabeaktion stattfindet, wird der Code im Modus "Steuerereignis" ausgeführt, in dem deButton erkennt, dass er gedrückt wurde, und anzeigt, dass er gedrückt wurde, indem TRUE zurückgegeben wird. Der "if (deButton (...)) {... Aktionscode ...} ist daher eine Möglichkeit, Aktionscode an die Schaltfläche anzuhängen, ohne einen Abschluss erstellen oder einen Ereignishandler schreiben zu müssen. Der DD_THROW ist ein Methode zum Beenden des Durchlaufs, wenn die Aktion ausgeführt wird, da die Aktion möglicherweise Anwendungsdaten geändert hat. Daher ist es ungültig, den Durchlauf "Steuerereignis" durch die Routine fortzusetzen. Wenn Sie dies mit dem Schreiben von Ereignishandlern vergleichen, sparen Sie das Schreiben dieser. und Sie können beliebig viele Steuerelemente verwenden.

Hinzugefügt: Sorry, ich sollte erklären, was ich mit dem Wort "pflegt" meine. Bei der ersten Ausführung der Prozedur (im SHOW-Modus) erstellt deButton eine Schaltflächensteuerung und merkt sich deren ID im FIFO. Bei nachfolgenden Durchläufen (im UPDATE-Modus) erhält deButton die ID vom FIFO, ändert sie bei Bedarf und legt sie wieder im FIFO ab. Im ERASE-Modus liest es es aus dem FIFO, zerstört es und legt es nicht zurück, wodurch es "Müll sammelt". Der deButton-Aufruf verwaltet also die gesamte Lebensdauer des Steuerelements und hält es in Übereinstimmung mit den Anwendungsdaten, weshalb ich sage, dass er es "verwaltet".

Der vierte Modus ist EVENT (oder CONTROL). Wenn der Benutzer ein Zeichen eingibt oder auf eine Schaltfläche klickt, wird dieses Ereignis abgefangen und aufgezeichnet, und die Prozedur deContents wird im EVENT-Modus ausgeführt. deButton erhält die ID seiner Tastensteuerung vom FIFO und fragt, ob dies die Steuerung ist, auf die geklickt wurde. Wenn dies der Fall war, wird TRUE zurückgegeben, damit der Aktionscode ausgeführt werden kann. Wenn nicht, wird nur FALSE zurückgegeben. deEdit(..., &myStringVar)Erkennt andererseits, ob das Ereignis dafür bestimmt war, und übergibt es in diesem Fall an das Bearbeitungssteuerelement. Anschließend wird der Inhalt des Bearbeitungssteuerelements in myStringVar kopiert. Zwischen dieser und der normalen UPDATE-Verarbeitung entspricht myStringVar immer dem Inhalt des Bearbeitungssteuerelements. So wird "gebunden". Die gleiche Idee gilt für Bildlaufleisten, Listenfelder, Kombinationsfelder und alle Steuerelemente, mit denen Sie Anwendungsdaten bearbeiten können.

Hier ist ein Link zu meiner Wikipedia-Bearbeitung: http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article


3
Es tut mir leid, Sie mit Antworten zu plagen, aber wenn ich das richtig verstehe, rücken Sie Ihre Berechnungen im Grunde immer näher an den Prozessor und von der Ausgabehardware weg. Dies ist eine unglaubliche Erkenntnis, da wir viel Geld in die Idee investieren, dass Objekte und Variablen durch Programmieren ziemlich einfach in den besten Maschinencode übersetzt werden, um die gleiche Ausgabe zu erzielen, was sicherlich überhaupt nicht der Fall ist! Obwohl wir Code beim Kompilieren optimieren können, ist es nicht möglich, zeitabhängige Aktionen zu optimieren. Verweigere Zeitabhängigkeit und lass Primitive die Arbeit machen !
Sova

2
@Joey: Nun, da Sie es erwähnen, die Idee einer Kontrollstruktur, die von einem FIFO ausgeführt wird, und von parallelen Co-Routinen, die von einer Jobwarteschlange ausgeführt werden, gibt es dort viele Gemeinsamkeiten.
Mike Dunlavey

2
Ich frage mich, wie nah Differential Execution an dem Ansatz der React.js-Bibliothek ist.
Brian

2
@Brian: Beim Überfliegen der Informationen verwendet react.js eine Diff-Funktion, um inkrementelle Updates an den Browser zu senden. Ich kann nicht sagen, ob die Diff-Funktion tatsächlich so leistungsfähig ist wie die differentielle Ausführung. Wie kann es willkürliche Änderungen behandeln, und es behauptet, die Bindung zu vereinfachen. Ob es im gleichen Maße gemacht wird, weiß ich nicht. Wie auch immer, ich denke es ist auf dem richtigen Weg. Paar Videos hier.
Mike Dunlavey

2
@MikeDunlavey, ich schreibe meine Tools mit einer Kombination aus OpenGL / IMGUI und reaktiver Programmierung über Model-, Model-View- und View-Ebenen. Ich werde jetzt nie wieder zum alten Stil zurückkehren. Danke für die Links deiner Videos.
Cthutu

12

Die differenzielle Ausführung ist eine Strategie zum Ändern des Codeflusses basierend auf externen Ereignissen. Dies geschieht normalerweise durch Manipulieren einer Datenstruktur, um die Änderungen aufzuzeichnen. Dies wird hauptsächlich in grafischen Benutzeroberflächen verwendet, aber auch für Dinge wie die Serialisierung, bei denen Sie Änderungen in einen vorhandenen "Status" zusammenführen.

Der Grundfluss ist wie folgt:

Start loop:
for each element in the datastructure: 
    if element has changed from oldDatastructure:
        copy element from datastructure to oldDatastructure
        execute corresponding subroutine (display the new button in your GUI, for example)
End loop:
Allow the states of the datastructure to change (such as having the user do some input in the GUI)

Dies hat nur wenige Vorteile. Zum einen geht es um die Trennung der Ausführung Ihrer Änderungen und die tatsächliche Manipulation der unterstützenden Daten. Welches ist schön für mehrere Prozessoren. Zweitens bietet es eine Methode mit geringer Bandbreite, um Änderungen in Ihrem Programm zu kommunizieren.


11

Überlegen Sie, wie ein Monitor funktioniert:

Es wird mit 60 Hz aktualisiert - 60 Mal pro Sekunde. Flackern flackern flackern 60 mal, aber deine Augen sind langsam und können es nicht wirklich sagen. Der Monitor zeigt an, was sich im Ausgabepuffer befindet. Diese Daten werden nur alle 1/60 Sekunde herausgezogen, egal was Sie tun.

Warum sollte Ihr Programm nun 60 Mal pro Sekunde den gesamten Puffer aktualisieren, wenn sich das Image nicht so oft ändert? Was ist, wenn Sie nur ein Pixel des Bildes ändern, sollten Sie den gesamten Puffer neu schreiben?


Dies ist eine Abstraktion der Grundidee: Sie möchten den Ausgabepuffer basierend auf den Informationen ändern, die auf dem Bildschirm angezeigt werden sollen. Sie möchten so viel CPU-Zeit und Pufferschreibzeit wie möglich sparen, damit Sie keine Teile des Puffers bearbeiten, die für den nächsten Bildschirmzug nicht geändert werden müssen.

Der Monitor ist von Ihrem Computer und Ihrer Logik (Programme) getrennt. Es liest aus dem Ausgabepuffer mit der Geschwindigkeit, mit der der Bildschirm aktualisiert wird. Wir möchten, dass unser Computer nicht mehr unnötig synchronisiert und neu gezeichnet wird. Wir können dies lösen, indem wir die Arbeitsweise mit dem Puffer ändern. Dies kann auf verschiedene Arten geschehen. Seine Technik implementiert eine verzögerte FIFO-Warteschlange - sie enthält das, was wir gerade an den Puffer gesendet haben. Die verzögerte FIFO-Warteschlange enthält keine Pixeldaten, sondern "Formprimitive" (dies können Pixel in Ihrer Anwendung sein, aber es können auch Linien, Rechtecke und einfach zu zeichnende Objekte sein, da es sich nur um Formen handelt und keine unnötigen Daten vorhanden sind erlaubt).

Sie möchten also Dinge vom Bildschirm zeichnen / löschen? Kein Problem. Aufgrund des Inhalts der FIFO-Warteschlange weiß ich, wie der Monitor im Moment aussieht. Ich vergleiche meine gewünschte Ausgabe (um neue Grundelemente zu löschen oder zu zeichnen) mit der FIFO-Warteschlange und ändere nur Werte, die geändert / aktualisiert werden müssen. Dies ist der Schritt, der ihm den Namen Differential Evaluation gibt.

Zwei verschiedene Arten, wie ich das schätze:

Der Erste: Mike Dunlavey verwendet eine Erweiterung für bedingte Anweisungen. Die FIFO-Warteschlange enthält viele Informationen (den "vorherigen Status" oder das aktuelle Material auf dem Monitor oder dem zeitbasierten Abrufgerät). Alles, was Sie hinzufügen müssen, ist der Status, den Sie als nächstes auf dem Bildschirm anzeigen möchten.

Jedem Slot, der ein Grundelement in der FIFO-Warteschlange enthalten kann, wird ein bedingtes Bit hinzugefügt.

0 means erase
1 means draw

Wir haben jedoch vorherigen Zustand:

Was 0, now 0: don't do anything;
Was 0, now 1: add it to the buffer (draw it);
Was 1, now 1: don't do anything;
Was 1, now 0: erase it from the buffer (erase it from the screen);

Dies ist elegant, denn wenn Sie etwas aktualisieren, müssen Sie wirklich nur wissen, welche Grundelemente Sie auf den Bildschirm zeichnen möchten. Bei diesem Vergleich wird ermittelt, ob ein Grundelement gelöscht oder in den Puffer eingefügt werden soll.

Das Zweiter: Dies ist nur ein Beispiel, und ich denke , dass Mike , was wirklich auf immer etwas ist , das in Design für alle Projekte von grundlegender Bedeutung sein sollte: Reduzieren Sie die (rechnerische) Komplexität des Designs durch das Schreiben der meisten rechenintensive Operationen wie computerbrain-Food oder so nah wie möglich. Respektieren Sie das natürliche Timing von Geräten.

Eine Neuzeichnungsmethode zum Zeichnen des gesamten Bildschirms ist unglaublich kostspielig, und es gibt andere Anwendungen, bei denen diese Erkenntnisse unglaublich wertvoll sind.

Wir "bewegen" niemals Objekte auf dem Bildschirm. "Verschieben" ist eine kostspielige Operation, wenn wir die physische Aktion des "Verschiebens" nachahmen möchten, wenn wir Code für so etwas wie einen Computermonitor entwerfen. Stattdessen flackern Objekte mit dem Monitor einfach ein und aus. Jedes Mal, wenn sich ein Objekt bewegt, ist es jetzt ein neuer Satz von Grundelementen, und der alte Satz von Grundelementen flackert ab.

Jedes Mal, wenn der Monitor aus dem Puffer zieht, haben wir Einträge, die aussehen

Draw bit    primitive_description
0           Rect(0,0,5,5);
1           Circ(0,0,2);
1           Line(0,1,2,5);

Niemals interagiert ein Objekt mit dem Bildschirm (oder einem zeitkritischen Abfragegerät). Wir können intelligenter damit umgehen als ein Objekt, wenn es gierig darum bittet, den gesamten Bildschirm zu aktualisieren, nur um eine Änderung anzuzeigen, die nur für sich selbst spezifisch ist.

Angenommen, wir haben eine Liste aller möglichen grafischen Grundelemente, die unser Programm erzeugen kann, und wir binden jedes Grundelement an eine Reihe von bedingten Anweisungen

if (iWantGreenCircle && iWantBigCircle && iWantOutlineOnMyCircle) ...

Natürlich ist dies eine Abstraktion, und tatsächlich könnte die Menge der Bedingungen, die ein bestimmtes primitives Ein / Aus darstellen, groß sein (vielleicht Hunderte von Flags, die alle als wahr ausgewertet werden müssen).

Wenn wir das Programm ausführen, können wir im Wesentlichen mit der gleichen Geschwindigkeit auf den Bildschirm zeichnen, mit der wir alle diese Bedingungen auswerten können. (Schlimmster Fall: Wie lange dauert die Auswertung der größten Menge bedingter Anweisungen?)

Jetzt können wir für jeden Status im Programm einfach alle Bedingungen auswerten und blitzschnell auf dem Bildschirm ausgeben ! (Wir kennen unsere Formprimitive und ihre abhängigen if-Anweisungen.)

Dies wäre wie der Kauf eines grafisch intensiven Spiels. Nur anstatt es auf Ihrer Festplatte zu installieren und über Ihren Prozessor zu betreiben, kaufen Sie ein brandneues Board, das das gesamte Spiel enthält und als Eingabe dient: Maus, Tastatur und als Ausgabe: Monitor. Unglaublich komprimierte bedingte Auswertung (als grundlegendste Form einer Bedingung sind Logikgatter auf Leiterplatten). Dies wäre natürlich sehr reaktionsschnell, bietet jedoch fast keine Unterstützung bei der Behebung von Fehlern, da sich das Design der gesamten Platine ändert, wenn Sie eine winzige Designänderung vornehmen (weil das "Design" so weit von der Natur der Platine entfernt ist ). Auf Kosten der Flexibilität und Klarheit bei der internen Darstellung von Daten haben wir eine erhebliche "Reaktionsfähigkeit" erreicht, da wir nicht mehr im Computer "denken". für die Leiterplatte basierend auf den Eingängen.

So wie ich es verstehe, besteht die Lektion darin, die Arbeit so aufzuteilen, dass Sie jedem Teil des Systems (nicht unbedingt nur Computer und Monitor) etwas geben, das es gut kann. Das "Computerdenken" kann in Form von Konzepten wie Objekten erfolgen ... Das Computergehirn wird gerne versuchen, dies alles für Sie durchzudenken, aber Sie können die Aufgabe erheblich vereinfachen, wenn Sie den Computer eindenken lassen können Begriffe von data_update und conditional_evals. Unsere menschlichen Abstraktionen von Konzepten in Code sind idealistisch und im Fall von internen Programmzeichnungsmethoden etwas zu idealistisch. Wenn alles, was Sie wollen, ein Ergebnis ist (Array von Pixeln mit korrekten Farbwerten) und Sie haben eine Maschine, die leicht kann Spucken Sie alle 1/60 Sekunde ein so großes Array aus und versuchen Sie, so viel blumiges Denken wie möglich aus dem Computergehirn zu entfernen, damit Sie sich auf das konzentrieren können, was Sie wirklich wollen: Ihre grafischen Aktualisierungen mit Ihren (schnellen) Eingaben zu synchronisieren und das natürliche Verhalten des Monitors.

Wie wird dies anderen Anwendungen zugeordnet? Ich würde gerne von anderen Beispielen hören, aber ich bin sicher, dass es viele gibt. Ich denke, dass alles, was ein Echtzeit- "Fenster" in den Status Ihrer Informationen bietet (variabler Status oder so etwas wie eine Datenbank ... ein Monitor ist nur ein Fenster in Ihren Anzeigepuffer), von diesen Erkenntnissen profitieren kann.


2
++ Ich schätze deine Einstellung dazu. Für mich war es zunächst ein Versuch, programmbeschriebene Anzeigen auf langsamen Geräten (z. B. 9600-Baud-Remote-Textterminals) durchzuführen, bei denen im Grunde genommen ein automatisiertes Diff durchgeführt und minimale Aktualisierungen übertragen wurden. Dann wurde ich darauf gedrängt, warum man dies nicht einfach mit brutaler Gewalt codiert. Die Antwort: Wenn die Oberflächenform des Codes wie eine einfache Farbe ist , ist sie kürzer, nahezu fehlerfrei und daher in einem Bruchteil der Entwicklungszeit erledigt. (Das ist, was ich als den Vorteil einer DSL betrachte.)
Mike Dunlavey

... Der freiwerdende Entwicklungsaufwand kann dann wieder in anspruchsvollere und dynamischere Displays investiert werden, die die Benutzer als reaktionsschnell und ansprechend empfinden. So erhalten Sie mehr UI-Knall für das Entwicklergeld.
Mike Dunlavey

... Beispiel: Diese App von vor ungefähr 10 Jahren: pharsight.com/products/prod_pts_using_dme.php
Mike Dunlavey

1
Das hat mich verständlich gemacht ... als du über Computerspiele gesprochen hast. Tatsächlich sind viele Spiele so codiert, wie Mike die Benutzeroberfläche erstellt. Eine Aktualisierungsliste, die mit jedem Frame durchlaufen wird.
Prof. Falken

Ein scheinbar verwandtes Beispiel für einige Ihrer Aussagen ist das Erkennen, ob eine Taste gedrückt wird oder gerade losgelassen wurde. Es ist leicht zu erkennen, ob eine Taste gedrückt wird oder nicht. Dies ist ein wahr / falsch-Wert aus Ihrer Low-Level-API. Um zu wissen, ob eine Taste gedrückt gehalten wird, müssen Sie wissen, in welchem ​​Zustand sie sich zuvor befand. Wenn sie von 0-> 1 ist, wurde sie nur gedrückt. Wenn es 1-> 1 ist, wird es gedrückt gehalten, wenn es von 1-> 0 ist, dann haben Sie gerade freigegeben.
Joshua Hedges

3

Ich finde dieses Konzept den Zustandsautomaten der klassischen digitalen Elektronik sehr ähnlich. Insbesondere diejenigen, die sich an ihre vorherige Ausgabe erinnern.

Eine Maschine, deren nächste Ausgabe von der aktuellen Eingabe und der vorherigen Ausgabe gemäß (IHR CODE HIER) abhängt. Dieser aktuelle Eingang ist nichts anderes als der vorherige Ausgang + (USER, INTERACT HERE).

Füllen Sie eine Oberfläche mit solchen Maschinen, und sie ist benutzerinteraktiv und repräsentiert gleichzeitig eine Schicht veränderbarer Daten. Aber zu diesem Zeitpunkt wird es immer noch dumm sein und nur die Benutzerinteraktion mit den zugrunde liegenden Daten widerspiegeln.

Verbinden Sie als nächstes die Maschinen auf Ihrer Oberfläche, lassen Sie sie gemäß (IHR CODE HIER) Notizen teilen, und jetzt machen wir es intelligent. Es wird ein interaktives Computersystem.

Sie müssen Ihre Logik also nur an zwei Stellen im obigen Modell bereitstellen. Der Rest wird vom Maschinendesign selbst erledigt. Das ist das Gute daran.


1
Ich scheine mich zu erinnern, dass ich ein Hardwaremodell im Sinn hatte, als mir dies einfiel.
Mike Dunlavey
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.