Как работает дифференциальное выполнение?


Я видел несколько упоминаний об этом в Stack Overflow, но смотрел на Википедию (соответствующая страница с тех пор была удалена) и на MFC dynamic dialog demo не сделал ничего, чтобы просветить меня. Может кто-нибудь объяснить это? Изучение принципиально другой концепции звучит неплохо.


исходя из ответов: я думаю, что я получаю лучшее чувство для него. Я думаю, я просто не смотрел на исходный код достаточно тщательно в первый раз. У меня смешанные чувства по поводу дифференциальное выполнение в этот момент. С одной стороны, это может значительно облегчить некоторые задачи. С другой стороны, получить его и запустить (то есть настроить его на выбранном вами языке) нелегко (я уверен, что это было бы, если бы я понял это лучше)...хотя я думаю, что набор инструментов для него нужно сделать только один раз, а затем расширить по мере необходимости. Я думаю, что для того, чтобы действительно понять это, мне, вероятно, нужно будет попробовать реализовать его на другом языке.

4 79

4 ответа:

Боже, Брайан, жаль, что я не видел твой вопрос раньше. Так как это мой "изобретение" (к лучшему или худшему), я мог бы помочь.

вставлено: как можно короче объяснение, которое я могу сделать, заключается в том, что если нормальное выполнение похоже на бросание мяч в воздухе и ловить его, то дифференциальный исполнения, как жонглирование.

объяснение@windfinder отличается от моего, и это нормально. Этот метод не легко обернуть голова вокруг, и мне потребовалось около 20 лет (время от времени), чтобы найти объяснения, которые работают. Позвольте мне дать ему еще один шанс:

  • что это?

мы все понимаем простую идею компьютера, шагающего через программу, принимая условные ветви на основе входных данных и делая вещи. (Предположим, что мы имеем дело только с простым структурированным кодом goto-less, return-less.) Этот код содержит последовательности операторов, основные структурированные условные обозначения, простые циклы и вызовы подпрограмм. (Забудьте о функциях, возвращающих значения на данный момент.)

Теперь представьте себе, что два компьютера выполняют один и тот же код в lock-step друг с другом и могут сравнивать заметки. Компьютер 1 работает с входными данными A, а компьютер 2 работает с входными данными B. Они работают шаг за шагом бок о бок. Если они приходят к условному оператору, например IF (test) .... ENDIF, и если у них есть разница во мнениях о том, является ли тест истинным, то тот, кто говорит проверьте, если false переходит к ENDIF и ждет вокруг своей сестры, чтобы догнать. (Вот почему код структурирован, поэтому мы знаем, что сестра в конечном итоге доберется до ENDIF.)

конечно, в дифференциальном исполнении (DE) это делается с помощью одного компьютера, имитирующего два.

Теперь, предположим, у вас есть только один набор входных данных, но вы хотите увидеть, как она изменилась с Время 1 до Время 2. Предположим, что программа выполняется в сериализатор/десериализатор. При выполнении вы одновременно сериализуете (записываете) текущие данные и десериализуете (считываете) прошлые данные (которые были записаны в последний раз, когда вы это делали). Теперь вы можете легко увидеть, какие различия между тем, что данные были в прошлый раз, и что это на этот раз.

файл, который вы пишете, и старый файл, из которого Вы читаете, вместе взятые составляют очередь или FIFO (first-in-first-out), но это не очень глубокая концепция.

  • для чего это хорошо?

это пришло мне в голову, когда я работал над графическим проектом, где пользователь мог построить небольшие процедуры отображения процессора, называемые "символами", которые можно было собрать в более крупные процедуры, чтобы нарисовать такие вещи, как диаграммы труб, резервуаров, клапанов и тому подобное. Мы хотели, чтобы диаграммы были "динамические" в том смысле, что они могут постепенно обновлять себя без необходимости перерисовывать всю диаграмму. (Аппаратное обеспечение было медленным по сегодняшним стандартам.) Я понял, что (например) процедура рисования полосы гистограммы может помнить свою старую высоту и просто постепенно обновлять себя.

Это звучит как ООП, не так ли? Однако вместо того, чтобы" делать "" объект", я мог бы воспользоваться предсказуемостью последовательности выполнения процедуры диаграммы. Я мог бы запишите высоту бара в последовательном байт-потоке. Затем, чтобы обновить изображение, я мог бы просто запустить процедуру в режиме, где он последовательно считывает свои старые параметры, пока он записывает новые параметры, чтобы быть готовым к следующему проходу обновления.

это кажется глупо очевидным и, казалось бы, сломать, как только процедура содержит условное, потому что тогда Новый Поток и старый поток будет выходить из синхронизации. Но потом до меня дошло, что, если они тоже сериализуются логическое значение условного теста, они могут вернуться в синхронизации. Потребовалось некоторое время, чтобы убедить себя, а затем доказать, что это будет всегда работа, при условии соблюдения простого правила ("Правило режима стирания").

конечным результатом является то, что пользователь может создавать эти "динамические символы" и собирать их в более крупные диаграммы, не беспокоясь о том, как они будут динамически обновляться, независимо от того, насколько сложным или структурно изменяемым является дисплей должен быть.

Так чего же он добивается? Это означает, что я могу построить диалог, написав процедуру для рисования элементов управления, и мне не нужно беспокоиться о фактическом запоминании объектов управления или о постепенном их обновлении, или заставляя их появляться/исчезать / двигаться, как того требуют условия. Результат намного меньше и проще исходного кода диалогового окна, примерно на порядок, и такие вещи, как динамическая компоновка или изменение количества элементов управления или наличие массивов или сеток элементов управления, тривиальны. Кроме того, такой элемент управления, как поле редактирования, может быть тривиально привязан к данным приложения, которые он редактирует, и он всегда будет доказуемо правильным, и мне никогда не придется иметь дело с его событиями. Ввод поля редактирования для приложения строковая переменная-это однострочное редактирование.

  • почему это трудно понять?

что я нашел труднее всего объяснить, так это то, что он требует иного мышления о программном обеспечении. Программисты настолько прочно привязаны к объектно-деятельностному представлению программного обеспечения, что они хотят знать, что такое объекты, какие классы, как они "строят" дисплей и как они обрабатывают события, что требуется Вишневая бомба, чтобы взорвать их из него. Что я пытаюсь донести вот что действительно важно что ты хочешь сказать? представьте, что вы создаете доменный язык (DSL), где все, что вам нужно сделать, это сказать ему: "я хочу редактировать переменную A здесь, переменную B там и переменную C там", и он волшебным образом позаботится об этом для вас. Например, в Win32 есть этот "язык ресурсов" для определения диалогов. Это совершенно хороший DSL, за исключением того, что он не заходит достаточно далеко. Он не "живет" на основном процедурном языке или обрабатывает события для вас или содержат циклы / условные обозначения / подпрограммы. Но это означает, что хорошо, и динамические диалоги пытается закончить работу.

Итак, другой способ мышления: чтобы написать программу, вы сначала находите (или изобретаете) соответствующий DSL и кодируете в нем как можно больше своей программы. Пусть это иметь дело со всеми объектами и действиями, которые существуют только для реализации.

Если вы хотите действительно понять дифференциальное исполнение и использовать это, есть несколько сложных вопросов, которые могут сбить вас с толку. Я однажды закодировал его в Lisp макросы, где эти сложные биты могут быть обработаны для вас, но в "нормальных" языках это требует некоторой дисциплины программиста, чтобы избежать ловушек.

Извините, что так многословно. Если у меня нет смысла, я был бы признателен, если бы вы указали на это, и я могу попытаться исправить это.

добавлено:

на Java Swing есть пример программы TextInputDemo. Это статический диалог, занимающий 270 строк (не считая списка из 50 состояний). В динамических диалогах (в МФЦ) это около 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 определяют общее содержимое пользовательского интерфейса. Строки 30-48 определяют элементы управления для редактирования одной записи пациента. Обратите внимание, что форма программы практически не принимает во внимание события в время, как будто все, что ему нужно было сделать, это создать дисплей один раз. Затем, если субъекты добавляются или удаляются или происходят другие структурные изменения, он просто повторно выполняется, как если бы он был воссоздан с нуля, за исключением того, что DE вызывает инкрементное обновление вместо этого. Преимущество заключается в том, что вам, программисту, не нужно уделять никакого внимания или писать какой-либо код, чтобы сделать инкрементные обновления пользовательского интерфейса, и они гарантированно правильны. Может показаться, что это повторное исполнение будет проблема производительности, но это не так, поскольку обновление элементов управления, которые не нужно менять, занимает порядка десятков наносекунд.

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 обнаруживает, что он был нажат, и означает, что он был нажат, возвращая TRUE. Таким образом, "если(дебют (...)) {... код действия ...}- это способ прикрепления кода действия к кнопке, без необходимости создавать закрытие или писать обработчик событий. DD_THROW-это способ завершения прохода при выполнении действия, поскольку действие может иметь измененные данные приложения, поэтому недопустимо продолжать проход "управляющего события" через процедуру. Если вы сравните это с написанием обработчиков событий, это сэкономит вам их запись, и это позволит вам иметь любое количество элементов управления.

добавлено: Извините, я должен объяснить, что я имею в виду под словом "поддерживает". При первом выполнении процедуры (в режиме показа) deButton создает кнопка управления и запоминает свой идентификатор в FIFO. При последующих проходах (в режиме обновления) deButton получает идентификатор из FIFO, изменяет его при необходимости и помещает обратно в FIFO. В режиме стирания он считывает его из FIFO, уничтожает его и не возвращает обратно, тем самым "собирая мусор". Таким образом, вызов deButton управляет всем временем жизни элемента управления, сохраняя его в соответствии с данными приложения, поэтому я говорю, что он "поддерживает" его.

четвертый режим событие (или КОНТРОЛЬ.) Когда пользователь вводит символ или нажимает кнопку, это событие улавливается и записывается, а затем процедура деконтирования выполняется в режиме событий. deButton получает идентификатор своего элемента управления button из FIFO и спрашивает, является ли это элементом управления, который был нажат. Если это было, он возвращает TRUE, чтобы код действия можно было выполнить. Если нет, то он просто возвращает FALSE. С другой стороны, deEdit(..., &myStringVar) определяет, было ли событие предназначено для него, и если да, то передает его в элемент управления редактирования, а затем копирует содержимое из элемента управления редактирования в myStringVar. Между этой и обычной обработкой обновлений myStringVar всегда равен содержимому элемента управления edit. Вот как делается" привязка". Та же идея применима к полосам прокрутки, спискам, полям со списком, любому виду элемента управления, который позволяет редактировать данные приложения.

вот ссылка на мое редактирование Википедии:http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article

дифференциальное выполнение-это стратегия изменения потока кода на основе внешних событий. Это обычно делается путем манипулирования какой-либо структурой данных для хроники изменений. Это в основном используется в графических пользовательских интерфейсах, но также используется для таких вещей, как сериализация, где вы объединяете изменения в существующее "состояние."

основной поток выглядит следующим образом:

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)

преимущества этого несколько. Во-первых, это разделение из выполнение ваших изменений, а также фактическое манипулирование вспомогательными данными. Что приятно для несколько процессоров. Во-вторых, он обеспечивает метод с низкой пропускной способностью о сообщаемых изменениях в вашей программе.

подумайте о том, как работает монитор:

он обновляется с частотой 60 Гц -- 60 раз в секунду. Мерцание мерцание мерцание 60 раз, но ваши глаза медленно и не может действительно рассказать. Монитор показывает все, что находится в выходном буфере; он просто вытягивает эти данные каждые 1/60th секунды независимо от того, что вы делаете.

теперь почему вы хотите, чтобы ваша программа обновляла весь буфер 60 раз в секунду, если изображение не должно меняться так часто? Что делать, если вы только изменить один пиксель изображения, вы должны переписать весь буфер?


это абстракция основной идеи: вы хотите изменить выходной буфер на основе того, какую информацию вы хотите отобразить на экране. Вы хотите сэкономить как можно больше времени процессора и времени записи буфера, поэтому вы не редактируете части буфера, которые не нужно менять для следующего нажатия экрана.

монитор отделен от вашего компьютера и логики (программ). Он читает выходной буфер с любой скоростью обновляет экран. Мы хотим, чтобы наш компьютер перестал синхронизировать и перерисовывать без необходимости. Мы можем решить эту проблему, изменив способ работы с буфером, что можно сделать различными способами. Его техника реализует очередь FIFO, которая находится на задержке - она содержит то, что мы только что отправили в буфер. Отложенная очередь FIFO не содержит пиксельных данных, она содержит "примитивы формы" (которые могут быть пикселями в вашем приложении, но также могут быть линиями, прямоугольниками, легко рисовать вещи, потому что они просто формы, никаких ненужных данных не допускается).

Итак, вы хотите рисовать/стирать вещи с экрана? Не проблема. Основываясь на содержании очереди FIFO, я знаю, как выглядит монитор на данный момент. Я сравниваю желаемый результат (чтобы стереть или нарисовать новые примитивы) с очередью FIFO и только изменяю значения, которые необходимо изменить/обновить. Это шаг, который дает ему имя дифференциальной оценки.

два различных пути в котором я ценю это:

Первый: Майк Данлейви использует условно-заявлением о продлении срока. Очередь 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);

это элегантно, потому что при обновлении чего-то вам действительно нужно только знать, какие примитивы вы хотите нарисовать на экране-это сравнение выяснит, следует ли стереть примитив или добавить/сохранить его в/в буфере.

Второй: Это всего лишь один пример, и я думаю, что Майк действительно получает то, что должно быть фундаментальным в дизайне для всех проектов: уменьшить (вычислительную) сложность дизайн, написав свои самые вычислительно интенсивные операции как computerbrain-food или как можно ближе. Уважайте естественное время работы устройств.

метод перерисовки для рисования всего экрана невероятно дорогостоящий, и есть другие приложения, где это понимание невероятно ценно.

мы никогда не" перемещаем " объекты по экрану. "Перемещение" является дорогостоящей операцией, если мы собираемся имитировать физическое действие "перемещения" при разработке кода для что-то вроде монитора компьютера. Вместо этого объекты в основном просто мерцают на мониторе. Каждый раз, когда объект движется, это теперь новый набор примитивов и старый набор примитивов мерцает.

каждый раз, когда монитор тянет из буфера, у нас есть записи, которые выглядят как

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

если мы запускаем программу, мы можем рисовать на экране по существу с той же скоростью, с которой мы можем оценить все эти условные обозначения. (Худший случай: сколько времени требуется для оценки наибольшего набора условных операторов.)

теперь, для любого состояния в программе, мы можем просто оценить все условные обозначения и вывести на экран молния-быстрая! (мы знаем наши примитивы формы и их зависимые if-утверждения.)

Это было бы похоже на покупку графически-интенсивного игра. Только вместо того, чтобы установить его на свой жесткий диск и запустить его через свой процессор, вы покупаете совершенно новую плату, которая держит всю игру и принимает в качестве ввода: мышь, клавиатуру и принимает в качестве вывода: монитор. Невероятно сжатая условная оценка (так как наиболее фундаментальной формой условного является логические элементы на печатных платах). Это, естественно, будет очень отзывчивым, но он почти не поддерживает исправление ошибок, так как весь дизайн платы меняется, когда вы делаете крошечный дизайн изменение (потому что "дизайн" настолько далек от природы печатной платы). За счет гибкости и ясности в том, как мы представляем данные внутри себя, мы получили значительную "отзывчивость", потому что мы больше не делаем" мышление " в компьютере; это все просто рефлекс для печатной платы на основе входов.

урок, как я понимаю, заключается в том, чтобы разделить труд таким образом, что вы даете каждую часть системы (не обязательно только компьютер и монитор) что-то он может сделать хорошо. "Компьютерное мышление" может быть сделано в терминах таких понятий, как объекты... Компьютерный мозг с удовольствием попытается все это продумать для вас, но вы можете значительно упростить задачу, если вы сможете позволить компьютеру думать в терминах data_update и conditional_evals. Наши человеческие абстракции понятий в коде идеалистичны, а в случае внутренней программы методы рисования немного чрезмерно идеалистичны. Когда все, что вы хотите, это результат (массив пикселей с правильными значениями цвета) и у вас есть машина, которая может легко выплюнуть массив, который Большой каждый 1/60-й секунды, попробуйте и устранить как можно больше цветочного мышления из компьютерного мозга, чтобы вы могли сосредоточиться на том, что вы действительно хотите: синхронизировать графические обновления с вашими (быстрыми) входами и естественным поведением монитора.

как это соотносится с другими приложениями? Я хотел бы услышать о других примерах, но я уверен, что есть много. Я думаю, что все, что обеспечивает "окно" в реальном времени в состояние вашей информации (переменное состояние или что-то вроде базы данных... монитор-это просто окно в ваш буфер отображения) может извлечь выгоду из этих идей.

Я нахожу эту концепцию очень похожей на государственные машины классической цифровой электроники. Особенно те, которые помнят свой предыдущий выход.

машина, следующий выход которой зависит от текущего входа и предыдущего выхода в соответствии с (ваш код здесь). Этот текущий вход-это не что иное, как предыдущий выход + (пользователь, взаимодействующий здесь).

заполните поверхность такими машинами, и она будет интерактивной для пользователя и в то же время представлять собой слой изменяемых данных. Но на этом этапе он все равно будет тупым, просто отражая взаимодействие пользователя с базовыми данными.

затем соедините машины на вашей поверхности, пусть они делятся заметками, согласно (ваш код здесь), и теперь мы делаем его интеллектуальным. Он станет интерактивной вычислительной системой.

таким образом, вы просто должны обеспечить свою логику в двух местах в приведенной выше модели; остальное заботится о самой конструкции машины. Вот что в этом хорошего.