web-dev-qa-db-ja.com

差分実行はどのように機能しますか?

Stack Overflowでこれについていくつか言及しましたが、ウィキペディア(関連ページは削除されました)と MFCダイナミックダイアログデモ を見つめても、私を啓発することはできませんでした。誰かがこれを説明できますか?根本的に異なる概念を学習するのはいいですね。


答えに基づいて:私はそれについてより良い感じを得ていると思います。私は、ソースコードを初めて注意深く見なかっただけだと思います。私はこの時点で差別的な実行について複雑な気持ちを持っています。一方では、特定のタスクをかなり簡単にすることができます。一方、それを起動して実行する(つまり、選択した言語で設定する)のは簡単ではありません(理解していれば間違いないでしょう)...一度作成するだけで、必要に応じて拡張できます。本当に理解するためには、おそらく別の言語で実装する必要があると思います。

81
Brian

ジー、ブライアン、あなたの質問をもっと早く見たかったのに。それは私の「発明」であるため(良くも悪くも)、私は助けることができるかもしれません。

挿入:私ができる最短の説明は、通常の実行が空中にボールを投げてキャッチするようなものであれば、差分実行はジャグリングのようなものだということです。

@windfinderの説明は私のものとは異なり、それで構いません。このテクニックは頭をかき回すのが簡単ではなく、効果的な説明を見つけるのに20年(オフとオン)かかりました。ここで別のショットをしてみましょう:

  • それは何ですか?

私たちは皆、コンピューターがプログラムをステップスルーし、入力データに基づいて条件分岐を実行し、物事を行うという単純なアイデアを理解しています。 (単純な構造化されたgoto-less、return-lessコードのみを扱っていると仮定します。)そのコードには、一連のステートメント、基本的な構造化された条件、単純なループ、サブルーチン呼び出しが含まれます。 (今のところ値を返す関数は忘れてください。)

次に、同じコードを互いにロックステップで実行し、ノートを比較できる2台のコンピューターを想像してください。コンピューター1は入力データAで実行され、コンピューター2は入力データBで実行されます。これらはステップバイステップで並んで実行されます。 IF(test).... ENDIFのような条件付きステートメントに到達し、テストが真であるかどうかについて意見の相違がある場合、テストが偽の場合にENDIFにスキップして待機するその姉妹は追いつくために。 (これがコードが構造化されている理由です。したがって、姉妹は最終的にENDIFに到達することがわかります。)

2台のコンピューターは相互に通信できるため、メモを比較して、2つの入力データセットと実行履歴がどのように異なるかについて詳細に説明できます。

もちろん、差分実行(DE)では、2台をシミュレートして1台のコンピューターで実行されます。

ここで、入力データが1セットしかないが、1時から2時までにどのように変化したかを確認するとします。実行しているプログラムがシリアライザー/デシリアライザーであるとします。実行すると、現在のデータをシリアル化(書き込み)し、過去のデータ(これを最後に書き込んだデータ)を逆シリアル化(読み込み)します。これで、前回のデータと今回のデータの違いを簡単に確認できます。

書き込み先のファイルと読み取り元の古いファイルは、一緒になってキューまたはFIFO(先入れ先出し)を構成しますが、それはあまり深い概念ではありません。

  • それは何の役に立つのですか?

グラフィックプロジェクトに取り組んでいたときに、ユーザーが「シンボル」と呼ばれる小さなディスプレイプロセッサルーチンを作成し、パイプ、タンク、バルブなどの図をペイントする大きなルーチンに組み込むことができました。ダイアグラム全体を再描画することなく、段階的に更新できるという意味で、ダイアグラムを「動的」にしたかったのです。 (今日の標準では、ハードウェアは低速でした。)(たとえば)バーチャートのバーを描画するルーチンは、古い高さを記憶し、それ自体を徐々に更新できることに気付きました。

これはOOPのようですね。ただし、「オブジェクト」を「作成」するのではなく、ダイアグラムプロシージャの実行シーケンスの予測可能性を利用できます。連続したバイトストリームでバーの高さを書き込むことができます。次に、イメージを更新するために、次の更新パスの準備ができるように、古いパラメーターを順次読み取りながら新しいパラメーターを書き込むモードでプロシージャを実行できます。

これはばかげて明白なようで、新しいストリームと古いストリームが同期しなくなるため、プロシージャに条件が含まれるとすぐに壊れるように見えます。しかし、その後、条件付きテストのブール値もシリアル化すると、syncに戻ることができることがわかりました。単純なルール(「消去モードルール」)が守られていれば、これがalwaysであることを自分自身に納得させ、証明するのにしばらくかかりました。

最終的な結果は、ユーザーがこれらの「動的シンボル」を設計し、表示がどのように複雑または構造的に変化しても、それらが動的に更新される方法を心配することなく、それらをより大きな図に組み立てることができるということです。

当時、私は視覚的なオブジェクト間の干渉を心配する必要がありました。ただし、今ではWindowsコントロールでこの手法を使用し、レンダリングの問題をWindowsに任せています。

それで何を達成しますか?つまり、コントロールをペイントする手順を記述してダイアログを作成でき、コントロールオブジェクトを実際に記憶したり、それらを段階的に更新したり、条件に応じて表示/消滅/移動したりすることを心配する必要はありません。その結果、ダイアログソースコードははるかに小さくシンプルになり、桁違いに大きくなります。また、動的なレイアウトやコントロールの数の変更、コントロールの配列やグリッドの作成などは簡単です。さらに、編集フィールドなどのコントロールは、編集中のアプリケーションデータに簡単にバインドでき、常に証明可能であり、そのイベントを処理する必要はありません。アプリケーション文字列変数の編集フィールドに入力することは、1行の編集です。

  • なぜ理解するのが難しいのですか?

説明するのが一番難しいと思ったのは、ソフトウェアについて異なる考え方をする必要があるということです。プログラマーはソフトウェアのオブジェクトアクションビューに固く結びついているため、オブジェクトとは何か、クラスとは何か、ディスプレイをどのように「構築」し、イベントをどのように処理するかを知りたいので、それらを爆破する爆弾。私が伝えようとしているのは、本当に重要なのは何を言う必要があるのか​​ということです?ドメイン固有言語(DSL)を構築していると想像してください「ここで変数Aを編集し、そこに変数Bを、そこに変数Cを編集したい」ので、魔法のように処理してくれます。たとえば、Win32には、ダイアログを定義するためのこの「リソース言語」があります。それは十分に行かないことを除いて、完全に良いDSLです。主要な手続き言語に「住む」ことも、イベントを処理することも、ループ/条件付き/サブルーチンを含むこともありません。しかし、それは意味があり、Dynamic Dialogsはジョブを終了しようとします。

だから、異なる考え方のモードは次のとおりです:プログラムを書くには、まず適切なDSLを見つけて(または発明して)、プログラムの多くの部分をコーディングしますその中で可能な限り。 itで、実装のためにのみ存在するすべてのオブジェクトとアクションを処理します。

差分実行を実際に理解して使用したい場合は、つまずく可能性のあるいくつかの厄介な問題があります。かつて [〜#〜] lisp [〜#〜] マクロでコーディングしました。これらのトリッキーなビットを処理できますが、「通常の」言語では、落とし穴を避けるためにプログラマーの訓練が必要です。

とても長くてすみません。意味が分からない場合は、指摘していただければ幸いです。修正を試みることができます。

追加:

Java Swing には、TextInputDemoというサンプルプログラムがあります。これは静的なダイアログで、270行を取ります(50の状態のリストはカウントしません)。ダイナミックダイアログ(MFC)では、約60行です。

_#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."));
}
_

追加:

病院の患者の配列を約40行のコードで編集するコードの例を次に示します。行1〜6は、「データベース」を定義します。行10〜23は、UIの全体的なコンテンツを定義します。行30〜48は、1人の患者の記録を編集するためのコントロールを定義します。プログラムの形式は、表示を一度作成するだけでよいかのように、イベントの通知を時間内にほとんど受け取らないことに注意してください。次に、サブジェクトが追加または削除されたり、他の構造的な変更が行われたりすると、DEが代わりに増分更新を実行することを除いて、ゼロから再作成されたかのように単純に再実行されます。利点は、UIのインクリメンタル更新を行うためにプログラマが注意を払ったりコードを書いたりする必要がなく、正しいことが保証されることです。この再実行はパフォーマンスの問題になると思われるかもしれませんが、変更する必要のないコントロールの更新には数十ナノ秒程度かかるため、そうではありません。

_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 }
_

追加:ブライアンは良い質問をしました、そして私は答えがここの本文に属していると思いました:

@Mike:「if(deButton(50、20、“ Add”)){」ステートメントが実際に何をしているのか明確ではありません。 deButton関数は何をしますか?また、FOR/ENDループは何らかのマクロを使用していますか? –ブライアン。

@Brian:はい、FOR/ENDおよびIFステートメントはマクロです。 SourceForgeプロジェクトには完全な実装があります。 deButtonはボタンコントロールを維持します。ユーザー入力アクションが発生すると、コードは「制御イベント」モードで実行されます。このモードでは、deButtonは押されたことを検出し、TRUEを返すことで押されたことを示します。したがって、「if(deButton(...)){...アクションコード...}は、クロージャーを作成したりイベントハンドラを作成したりすることなく、アクションコードをボタンに添付する方法です。DD_THROWはアクションによってアプリケーションデータが変更された可能性があるため、アクションが実行されたときにパスを終了する方法。したがって、ルーチンを介して「制御イベント」パスを続行することは無効です。また、任意の数のコントロールを使用できます。

追加:申し訳ありませんが、「維持する」という言葉の意味を説明する必要があります。 (SHOWモードで)プロシージャが最初に実行されると、deButtonはボタンコントロールを作成し、FIFOでそのIDを記憶します。後続のパス(更新モード)で、deButtonはFIFOからIDを取得し、必要に応じて変更し、FIFOに戻します。 ERASEモードでは、FIFOから読み取り、破棄し、元に戻さないため、「ガベージコレクション」されます。そのため、deButton呼び出しは、コントロールのライフタイム全体を管理し、アプリケーションデータとの一致を維持します。そのため、コントロールを「維持する」と言います。

4番目のモードはEVENT(またはCONTROL)です。ユーザーが文字を入力するかボタンをクリックすると、そのイベントがキャッチされて記録され、deContentsプロシージャがEVENTモードで実行されます。 deButtonは、FIFOからボタンコントロールのIDを取得し、これがクリックされたコントロールであるかどうかを尋ねます。クリックされた場合、アクションコードを実行できるようにTRUEを返します。一方、deEdit(..., &myStringVar)は、イベントが意図されたイベントであるかどうかを検出し、イベントがエディットコントロールに渡された場合、エディットコントロールの内容をmyStringVarにコピーします。通常のUPDATE処理では、myStringVarは常にエディットコントロールの内容と等しくなります。これが「バインド」の方法です。同じアイデアが、スクロールバー、リストボックス、コンボボックス、アプリケーションデータを編集できるコントロールに適用されます。

ここに私のウィキペディア編集へのリンクがあります: http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article

93
Mike Dunlavey

差分実行は、外部イベントに基づいてコードのフローを変更するための戦略です。これは通常、何らかの種類のデータ構造を操作して変更を記録することによって行われます。これは主にグラフィカルユーザーインターフェイスで使用されますが、既存の「状態」に変更をマージするシリアル化などにも使用されます。

基本的なフローは次のとおりです。

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)

これにはいくつかの利点があります。 1つは、変更の実行と、サポートデータの実際の操作の分離です。これは、複数のプロセッサに適しています。 2つ目は、プログラムの変更を伝える低帯域幅の方法を提供します。

12
Alex

モニターの仕組みを考えてください:

60 Hzで更新されます-1秒間に60回。フリッカーフリッカーフリッカーは60回点滅しますが、あなたの目は遅く、実際にわかりません。モニターは、出力バッファーにあるものをすべて表示します。何をしても、このデータを1/60秒ごとにドラッグするだけです。

今度は、イメージがそれほど頻繁に変更されない場合に、なぜプログラムが1秒間に60回バッファ全体を更新するのでしょうか?画像の1ピクセルのみを変更する場合、バッファ全体を書き換える必要がありますか?


これは基本的な考え方の抽象化です。画面に表示する情報に基づいて出力バッファを変更する必要があります。 CPU時間とバッファ書き込み時間をできるだけ節約したいので、次の画面プルのために変更する必要のないバッファの部分を編集しないでください。

モニターは、コンピューターおよびロジック(プログラム)とは別のものです。画面を更新する速度に関係なく、出力バッファーから読み取ります。コンピューターの同期と不必要な再描画を停止する必要があります。これを解決するには、さまざまな方法で行うことができるバッファーの操作方法を変更します。彼の手法は、遅延中のFIFOキューを実装しています。これは、バッファに送信したばかりのものを保持します。遅延FIFOキューは、ピクセルデータを保持しません。 「形状プリミティブ」を保持します(アプリケーションのピクセルの場合がありますが、線、長方形、単なる図形であるため描画しやすいものでもかまいません。不要なデータは許可されません)。

それで、あなたはスクリーンからものを描き/消去したいですか?問題ない。 FIFOキューの内容に基づいて、現時点でモニターがどのように見えるかを知っています。(新しいプリミティブを消去または描画するために)希望の出力をFIFOキューおよび変更/更新が必要な値のみを変更するこれは、Differential Evaluationという名前を付けるステップです。

2つの異なる方法私はこれに感謝しています:

最初: Mike Dunlaveyは条件付きステートメント拡張を使用します。 FIFOキューには多くの情報が含まれています(「以前の状態」またはモニターまたは時間ベースのポーリングデバイスの現在のもの)。これに追加する必要があるのは、目的の状態だけです次に画面に表示されます。

FIFOキューでプリミティブを保持できるすべてのスロットに条件ビットが追加されます。

0 means erase
1 means draw

ただし、以前の状態があります。

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);

これはエレガントです。何かを更新するとき、画面に描画するプリミティブを知る必要があるだけです。この比較では、プリミティブを消去するか、バッファに追加/保持するかがわかります。

2番目:これはほんの一例であり、Mikeが本当に得ていることは、すべてのプロジェクトの設計において基本的なものであると思います。計算量の多い操作は、コンピュータ頭脳の食べ物のように、または可能な限り近くにあります。デバイスの自然なタイミングを尊重します。

画面全体を描画するための再描画メソッドは非常にコストがかかり、この洞察が非常に貴重な他のアプリケーションがあります。

画面上でオブジェクトを「移動」することはありません。 「移動」は、コンピューターモニターのようなもののコードを設計するときに「移動」の物理的動作を模倣しようとする場合、コストのかかる操作です。代わりに、オブジェクトは基本的にモニターで点滅します。オブジェクトが移動するたびに、新しいプリミティブのセットになり、古いプリミティブのセットが点滅します。

モニターがバッファーからプルするたびに、次のようなエントリーがあります。

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

オブジェクトが画面(または時間依存ポーリングデバイス)と対話することはありません。オブジェクト自体が画面全体の更新を貪欲に要求して、それ自体に固有の変更を表示するときよりも、インテリジェントに処理できます。

プログラムで生成可能なすべてのグラフィカルプリミティブのリストがあり、各プリミティブを条件ステートメントのセットに結び付けているとします

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

もちろん、これは抽象化であり、実際には、特定のプリミティブのオン/オフを表す条件セットが大きくなる可能性があります(おそらく、すべてがtrueと評価されなければならない何百ものフラグ)。

プログラムを実行すると、これらすべての条件を評価できる本質的に同じレートで画面に描画できます。 (最悪の場合:条件ステートメントの最大セットを評価するのにかかる時間。)

これで、プログラムの任意の状態について、すべての条件を単純に評価して画面に出力できますlightning-quick!(形状プリミティブと依存するifステートメント。)

これは、グラフィカルなゲームを購入するようなものです。 HDDにインストールしてプロセッサで実行する代わりに、ゲーム全体を保持し、入力として使用する新しいボード、マウス、キーボード、出力として使用するモニターを購入します。信じられないほど凝縮された条件付き評価(条件付きの最も基本的な形式は、回路基板上の論理ゲートです)。当然、これは非常に応答性が高いですが、小さなデザイン変更を行うとボード全体のデザインが変更されるため、バグの修正はほとんどサポートされません(「デザイン」は回路基板の性質からあまり離れているため) )。内部でデータを表現する方法の柔軟性と明確さを犠牲にして、コンピューターで「思考」を行っていないため、重要な「応答性」を獲得しています。入力に基づいた回路基板の場合は、すべてreflexです。

私が理解しているように、教訓は、システムの各部分(必ずしもコンピューターとモニターだけでなく)に何か良いことをするように労力を分けることです。 「コンピューター思考」は、オブジェクトのような概念の観点から行うことができます...コンピューターの脳は喜んでこれを試行錯誤しますが、コンピューターに思考させることができれば、タスクを大幅に簡素化できますdata_updateおよびconditional_evalsの条件。私たちの人間による概念のコードへの抽象化は理想主義的であり、内部プログラムの描画メソッドの場合、少し過度に理想主義的です。必要なのは結果(正しいカラー値を持つピクセルの配列)であり、簡単に1ごとに大きな配列を吐き出すことができるマシンがある場合/ 60秒、コンピューターの脳から可能な限り花のような思考を排除して、本当に欲しいものに集中できるようにします:グラフィカルな更新を(高速)入力とモニターの自然な動作と同期させます。

これは他のアプリケーションにどのようにマッピングされますか?他の例を聞きたいのですが、きっとたくさんあると思います。これらの洞察は、情報の状態にリアルタイムの「ウィンドウ」を提供するもの(変数状態やデータベースのようなもの...モニターは単なるディスプレイバッファーのウィンドウ)に役立つと思います。

11
sova

この概念は、古典的なデジタルエレクトロニクスのステートマシンに非常によく似ています。特に、以前の出力を記憶しているもの。

次の出力が(ここのコード)に従って現在の入力と前の出力に依存するマシン。この現在の入力は、前の出力+(ユーザー、ここで対話する)に他なりません。

このようなマシンでサーフェスを埋めれば、ユーザーとの対話が可能になり、同時に変更可能なデータのレイヤーを表します。しかし、この段階では、ユーザーの操作を基礎データに反映しているだけで、まだ馬鹿げています。

次に、表面上のマシンを相互接続し、(ここのコード)に従ってメモを共有させ、今度はインテリジェントにします。インタラクティブなコンピューティングシステムになります。

したがって、上記のモデルの2つの場所でロジックを提供する必要があります。残りの部分は、機械の設計自体によって処理されます。それはそれについて良いことです。

3
wingman