Parallelität, Coroutinen, Ereignismaschinen, ... lebende Mathematik

Paralleles Rechnen fasziniert mit der Unerwartetheit seines Verhaltens. Das gemeinsame Verhalten von Prozessen kann jedoch nicht unvorhersehbar sein. Nur in diesem Fall kann er in seinen Macken studiert und verstanden werden. Die moderne Multithread-Parallelität ist einzigartig. Im wahrsten Sinne des Wortes. Und das ist alles seine schlechte Essenz. Die Essenz, die beeinflusst werden kann und sollte. Die Essenz, die in guter Weise schon lange hätte geändert werden müssen ...

Obwohl es eine andere Option gibt. Es besteht keine Notwendigkeit, etwas zu ändern und / oder etwas zu beeinflussen. Lassen Sie es Multithreading und Coroutinen geben, lassen Sie es sein ... und parallele automatische Programmierung (AP). Lassen Sie sie miteinander konkurrieren und ergänzen Sie sich, wenn nötig und möglich. In diesem Sinne hat die moderne Parallelität mindestens ein Plus - Sie können dies tun.

Nun, lass uns konkurrieren !?

1. Von seriell zu parallel


Betrachten Sie die Programmierung der einfachsten arithmetischen Gleichung:

C2 = A + B + A1 + B1; (1)

Es gebe Blöcke, die einfache arithmetische Operationen implementieren. In diesem Fall sind Summierblöcke ausreichend. Eine klare und genaue Vorstellung von der Anzahl der Blöcke, ihrer Struktur und den Beziehungen zwischen ihnen ergibt Reis. 1. Und in Abb. 2. Die Konfiguration des VKP (a) -Mediums ist zur Lösung von Gleichung (1) angegeben.

Bild
Abb. 1. Strukturmodell von Prozessen

Das Strukturdiagramm in Abb. 1 entspricht jedoch einem System aus drei Gleichungen:

= A + B;
C1 = A1 + B; (2)
C2 = C + C1;

Gleichzeitig (2) ist dies eine parallele Implementierung von Gleichung (1), die ein Algorithmus zum Summieren eines Arrays von Zahlen ist, der auch als Verdopplungsalgorithmus bekannt ist. Hier wird das Array durch vier Zahlen A, B, A1, B1 dargestellt, die Variablen C und C1 sind Zwischenergebnisse, und C2 ist die Summe des Arrays.

Bild
Abb. 2. Art der Dialoge für die Konfiguration von drei parallelen Prozessen.

Zu den Implementierungsmerkmalen gehört die Kontinuität des Betriebs, wenn eine Änderung der Eingabedaten zu einer Nachzählung des Ergebnisses führt. Nach dem Ändern der Eingangsdaten dauert es zwei Taktzyklen, und wenn die Blöcke in Reihe geschaltet sind, wird der gleiche Effekt in drei Taktzyklen erzielt. Und je größer das Array, desto größer der Geschwindigkeitsgewinn.

2. Parallelität als Problem


Seien Sie nicht überrascht, wenn Sie viele Argumente für eine bestimmte parallele Lösung nennen, aber sie schweigen über mögliche Probleme, die in der normalen sequentiellen Programmierung völlig fehlen. Der Hauptgrund für die ähnliche Interpretation des Problems der korrekten Umsetzung der Parallelität. Sie sagen am wenigsten über sie. Wenn überhaupt, sagen sie. Wir werden darauf in dem Teil eingehen, der sich auf den parallelen Zugriff auf Daten bezieht.

Jeder Prozess kann als viele aufeinanderfolgende unteilbare Schritte dargestellt werden. Für viele Prozesse werden bei jedem dieser Schritte Aktionen, die zu allen Prozessen gehören, gleichzeitig ausgeführt. Und hier können wir durchaus auf ein Problem stoßen, das sich im folgenden elementaren Beispiel manifestiert.

Angenommen, es gibt zwei parallele Prozesse, die dem folgenden Gleichungssystem entsprechen:

c = a + b; (3)
a = b + c;

Angenommen, den Variablen a, b, c werden die Anfangswerte 1, 1, 0 zugewiesen. Wir können erwarten, dass das Berechnungsprotokoll für die fünf Schritte wie folgt lautet:

a              b               c
1.000       1.000       0.000      
1.000       1.000       2.000       
3.000       1.000       2.000       
3.000       1.000       4.000       
5.000       1.000       4.000        
5.000       1.000       6.000   

Bei der Bildung gingen wir davon aus, dass die Operatoren parallel (gleichzeitig) und innerhalb einer diskreten Maßnahme (Schritt) ausgeführt werden. Bei Schleifenanweisungen handelt es sich um eine Iteration der Schleife. Wir können auch davon ausgehen, dass die Variablen bei der Berechnung zu Beginn eines diskreten Maßes feste Werte haben und ihre Änderung am Ende erfolgt. Dies steht im Einklang mit der tatsächlichen Situation, in der es einige Zeit dauert, bis der Vorgang abgeschlossen ist. Es ist oft mit einer Verzögerung verbunden, die einem bestimmten Block inhärent ist.

Aber höchstwahrscheinlich erhalten Sie so etwas wie dieses Protokoll:

   a              b               c
1.000       1.000       0.000      
3.000       1.000       2.000       
5.000       1.000       4.000       
7.000       1.000       6.000       
9.000       1.000       8.000       
11.000     1.000     10.000       

Es entspricht der Arbeit eines Prozesses, der zwei aufeinanderfolgende Anweisungen in einem Zyklus ausführt:

c = a + b; a = b + c; (4)

Es kann jedoch vorkommen, dass die Ausführung der Anweisungen genau umgekehrt ist und das Protokoll dann wie folgt lautet:

   a              b               c
1.000       1.000       0.000      
1.000       1.000       2.000       
3.000       1.000       4.000       
5.000       1.000       6.000       
7.000       1.000       8.000       
9.000       1.000     10.000       

Bei der Multithread-Programmierung ist die Situation noch schlimmer. Ohne Synchronisation der Prozesse ist es nicht nur schwierig, die Reihenfolge des Starts der Bediener vorherzusagen, sondern ihre Arbeit wird auch überall unterbrochen. All dies kann sich nur auf die Ergebnisse der gemeinsamen Arbeit der Betreiber auswirken.

Im Rahmen der AP-Technologie ist das Arbeiten mit gängigen Prozessvariablen einfach und korrekt zulässig. Hier sind meist keine besonderen Anstrengungen erforderlich, um Prozesse zu synchronisieren und mit Daten zu arbeiten. Es wird jedoch notwendig sein, Aktionen herauszugreifen, die als bedingt augenblicklich und unteilbar betrachtet werden, sowie automatische Prozessmodelle zu erstellen. In unserem Fall sind die Aktionen Summationsoperatoren, und Automaten mit zyklischen Übergängen sind für ihren Start verantwortlich.
Listing 1 zeigt den Code für einen Prozess, der die Summenoperation implementiert. Sein Modell ist eine endliche Zustandsmaschine (siehe Fig. 3) mit einem Zustand und einem bedingungslosen Schleifenübergang, für die die einzige Aktion y1, die die Operation der Summierung zweier Variablen durchgeführt hat, das Ergebnis in die dritte setzt.

Bild
Abb. 3. Automatisiertes Modell der Summationsoperation

Listing 1. Implementierung eines Automatenprozesses für eine Summenoperation
#include "lfsaappl.h"
class FSumABC :
    public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FSumABC(nameFsa); }
    bool FCreationOfLinksForVariables();
    FSumABC(string strNam);
    CVar *pVarA;		//
    CVar *pVarB;		//
    CVar *pVarC;		//
    CVar *pVarStrNameA;        //
    CVar *pVarStrNameB;        //
    CVar *pVarStrNameC;        //
protected:
    void y1();
};

#include "stdafx.h"
#include "FSumABC.h"

static LArc TBL_SumABC[] = {
    LArc("s1",	"s1","--", "y1"),
    LArc()
};

FSumABC::FSumABC(string strNam):
    LFsaAppl(TBL_SumABC, strNam, nullptr, nullptr)
{ }

bool FSumABC::FCreationOfLinksForVariables() {
    pVarA = CreateLocVar("a", CLocVar::vtBool, "variable a");
    pVarB = CreateLocVar("b", CLocVar::vtBool, "variable c");
    pVarC = CreateLocVar("c", CLocVar::vtBool, "variable c");
    pVarStrNameA = CreateLocVar("strNameA", CLocVar::vtString, "");
    string str = pVarStrNameA->strGetDataSrc();
    if (str != "") { pVarA = pTAppCore->GetAddressVar(pVarStrNameA->strGetDataSrc().c_str(), this); }
    pVarStrNameB = CreateLocVar("strNameB", CLocVar::vtString, "");
    str = pVarStrNameB->strGetDataSrc();
    if (str != "") { pVarB = pTAppCore->GetAddressVar(pVarStrNameB->strGetDataSrc().c_str(), this); }
    pVarStrNameC = CreateLocVar("strNameC", CLocVar::vtString, "");
    str = pVarStrNameC->strGetDataSrc();
    if (str != "") { pVarC = pTAppCore->GetAddressVar(pVarStrNameC->strGetDataSrc().c_str(), this); }
    return true;
}

void FSumABC::y1() { 
    pVarC->SetDataSrc(this, pVarA->GetDataSrc() + pVarB->GetDataSrc()); 
}


Wichtig oder vielmehr sogar notwendig ist hier die Verwendung der Umgebungsvariablen der KPdSU. Ihre „Schatteneigenschaften“ sorgen für das richtige Zusammenspiel von Prozessen. Darüber hinaus können Sie in der Umgebung den Betriebsmodus ändern, ohne die Aufzeichnung von Variablen im Zwischenschattenspeicher. Eine Analyse der in diesem Modus erhaltenen Protokolle ermöglicht es uns, die Notwendigkeit der Verwendung von Schattenvariablen zu überprüfen.

3. Und die Coroutinen? ...


Es wäre schön zu wissen, wie die durch die Kotlin-Sprache dargestellten Coroutinen mit der Aufgabe umgehen werden. Nehmen wir als Lösungsvorlage das Programm, das in der Diskussion von [1] berücksichtigt wurde. Es hat eine Struktur, die leicht auf das gewünschte Aussehen reduziert werden kann. Ersetzen Sie dazu die darin enthaltenen logischen Variablen durch einen numerischen Typ und fügen Sie eine weitere Variable hinzu. Anstelle von logischen Operationen verwenden wir die Summationsoperation. Der entsprechende Code ist in Listing 2 aufgeführt.

Listing 2. Kotlin-Parallel-Summationsprogramm
import kotlinx.coroutines.*

suspend fun main() =
    // Structured concurrency: if any child coroutine fails,
    // everything else will be cancelled
    coroutineScope {
        var a = 1
        var b = 1
        var c = 0;
        // Use default thread pool 
        withContext(Dispatchers.Default) {
            for (i in 0..4) {
               var res = listOf(async { a+b }, async{ b+c }).map { it.await() }
			c = res[0]
			a = res[1]
               println("$a, $b, $c")
            }
        }
    }


Es gibt keine Fragen zum Ergebnis dieses Programms, da es stimmt genau mit dem ersten der oben genannten Protokolle überein.

Die Konvertierung des Quellcodes war jedoch nicht so offensichtlich, wie es scheinen mag, weil Es schien natürlich, das folgende Codefragment zu verwenden:

listOf(async {  = a+b }, async{  = b+c })

Wie Tests gezeigt haben (dies kann online auf der Kotlin-Website - kotlinlang.org/#try-kotlin - erfolgen ), führt seine Verwendung zu einem völlig unvorhersehbaren Ergebnis, das sich auch von Start zu Start ändert. Und nur eine genauere Analyse des Quellprogramms führte zum richtigen Code.

Code, der aus Sicht der Funktionsweise des Programms einen Fehler enthält, aus Sicht der Sprache jedoch legitim ist, lässt uns um die Zuverlässigkeit der darauf befindlichen Programme fürchten. Diese Meinung kann wahrscheinlich von Kotlin-Experten in Frage gestellt werden. Die Leichtigkeit, einen Fehler zu machen, der nicht nur durch ein mangelndes Verständnis der „Coroutine-Programmierung“ erklärt werden kann, drängt dennoch beharrlich auf solche Schlussfolgerungen.

4. Eventmaschinen in Qt


Zuvor haben wir festgestellt, dass ein Ereignisautomat in seiner klassischen Definition kein Automat ist. Er ist gut oder schlecht, aber bis zu einem gewissen Grad ist die Ereignismaschine immer noch ein Verwandter klassischer Maschinen. Ist es fern, nah, aber wir müssen direkt darüber sprechen, damit es keine falschen Vorstellungen darüber gibt. Wir haben in [2] darüber gesprochen, aber alles, um es fortzusetzen. Jetzt werden wir dies tun, indem wir andere Beispiele für die Verwendung von Ereignismaschinen in Qt untersuchen.

Natürlich kann ein Ereignisautomat als ein entarteter Fall eines klassischen Automaten mit einer unbestimmten und / oder variablen Zyklusdauer betrachtet werden, die mit einem Ereignis verbunden ist. Die Möglichkeit einer solchen Interpretation wurde im vorigen Artikel gezeigt, als nur ein und darüber hinaus ein eher spezifisches Beispiel gelöst wurde (siehe ausführlicher [2]). Als nächstes werden wir versuchen, diese Lücke zu schließen.

Die Qt-Bibliothek verknüpft nur Maschinenübergänge mit Ereignissen, was eine schwerwiegende Einschränkung darstellt. In derselben UML-Sprache ist ein Übergang beispielsweise nicht nur einem Ereignis zugeordnet, das als auslösendes Ereignis bezeichnet wird, sondern auch einer Schutzbedingung - einem logischen Ausdruck, der nach dem Empfang des Ereignisses berechnet wird [3]. In MATLAB wird die Situation weiter gemildert und klingt folgendermaßen: „Wenn der Name des Ereignisses nicht angegeben wird, erfolgt der Übergang, wenn ein Ereignis eintritt“ [4]. Aber hier und da ist die Hauptursache des Übergangs das Ereignis / die Ereignisse. Aber was ist, wenn es keine Ereignisse gibt?

Wenn es keine Ereignisse gibt, können Sie versuchen, sie zu erstellen. Listing 3 und Abbildung 4 zeigen, wie dies getan wird, indem der Nachkomme der Automatenklasse LFsaAppl der VKPa-Umgebung als "Wrapper" der Ereignis-Qt-Klasse verwendet wird. Hier sendet die Aktion y2 mit einer Periodizität der diskreten Zeit des Automatenraums ein Signal, das den Beginn des Übergangs des Qt-Automaten initiiert. Letzterer startet mit der Methode s0Exited die Aktion y1, die die Summierungsoperation implementiert. Beachten Sie, dass die Ereignismaschine durch die Aktion y3 streng nach Überprüfung der Initialisierung lokaler Variablen der LFsaAppl-Klasse erstellt wird.

Bild
Abb. 4. Kombination von Klassik- und Eventmaschinen

Listing 3. Implementierung eines Summationsmodells mit einem Ereignisautomaten
#include "lfsaappl.h"
class QStateMachine;
class QState;

class FSumABC :
    public QObject,
    public LFsaAppl
{
    Q_OBJECT
...
protected:
    int x1();
    void y1(); void y2(); void y3(); void y12();
signals:
    void GoState();
private slots:
    void s0Exited();
private:
    QStateMachine * machine;
    QState * s0;
};

#include "stdafx.h"
#include "FSumABC.h"
#include <QStateMachine>
#include <QState>

static LArc TBL_SumABC[] = {
    LArc("st",	"st","^x1",	"y12"),
    LArc("st",	"s1","x1",	"y3"),
    LArc("s1",	"s1","--",	"y2"),
    LArc()
};

FSumABC::FSumABC(string strNam):
    QObject(),
    LFsaAppl(TBL_SumABC, strNam, nullptr, nullptr)
{ }
...
int FSumABC::x1() { return pVarA&&pVarB&&pVarC; }

void FSumABC::y1() {
    pVarC->SetDataSrc(this, pVarA->GetDataSrc() + pVarB->GetDataSrc());
}
//
void FSumABC::y2() { emit GoState(); }
//
void FSumABC::y3() {
    s0 = new QState();
    QSignalTransition *ps = s0->addTransition(this, SIGNAL(GoState()), s0);
    connect (s0, SIGNAL(entered()), this, SLOT(s0Exited()));
    machine = new QStateMachine(nullptr);
    machine->addState(s0);
    machine->setInitialState(s0);
    machine->start();
}
//    
void FSumABC::y12() { FInit(); }

void FSumABC::s0Exited() { y1(); }


Oben haben wir eine sehr einfache Maschine implementiert. Genauer gesagt, eine Kombination aus klassischen und Ereignisautomaten. Wenn die vorherige Implementierung der FSumABC-Klasse durch die erstellte ersetzt wird, gibt es einfach keine Unterschiede in der Anwendung. Bei komplexeren Modellen beginnen sich die begrenzten Eigenschaften ereignisgesteuerter Automaten jedoch vollständig zu manifestieren. Zumindest bereits beim Erstellen eines Modells. Listing 4 zeigt die Implementierung des Modells eines AND-NOT-Elements in Form eines Ereignisautomaten (weitere Einzelheiten zum verwendeten Automatenmodell eines AND-NOT-Elements finden Sie in [2]).

Listing 4. Implementieren eines Elementmodells UND NICHT von einer Ereignismaschine
#include <QObject>

class QStateMachine;
class QState;

class MainWindow;
class ine : public QObject
{
    Q_OBJECT
public:
    explicit ine(MainWindow *parent = nullptr);
    bool bX1, bX2, bY;
signals:
    void GoS0();
    void GoS1();
private slots:
    void s1Exited();
    void s0Exited();
private:
    QStateMachine * machine;
    QState * s0;
    QState * s1;
    MainWindow *pMain{nullptr};
friend class MainWindow;
};

#include "ine.h"
#include <QStateMachine>
#include <QState>
#include "mainwindow.h"
#include "ui_mainwindow.h"

ine::ine(MainWindow *parent) :
    QObject(parent)
{
    pMain = parent;
    s0 = new QState();
    s1 = new QState();

    s0->addTransition(this, SIGNAL(GoS1()), s1);
    s1->addTransition(this, SIGNAL(GoS0()), s0);

    connect (s0, SIGNAL(entered()), this, SLOT(s0Exited()));
    connect (s1, SIGNAL(entered()), this, SLOT(s1Exited()));


    machine = new QStateMachine(nullptr);
    machine->addState(s0);
    machine->addState(s1);
    machine->setInitialState(s1);
    machine->start();
}

void ine::s1Exited() {
    bY = !(bX1&&bX2);
    pMain->ui->checkBoxY->setChecked(bY);
}

void ine::s0Exited() {
    bY = !(bX1&&bX2);
    pMain->ui->checkBoxY->setChecked(bY);
}


Es wird deutlich, dass Ereignisautomaten in Qt ausschließlich auf Moore-Automaten basieren. Dies schränkt die Fähigkeiten und die Flexibilität des Modells ein Aktionen sind nur Zuständen zugeordnet. Infolgedessen ist es beispielsweise unmöglich, zwischen zwei Übergängen vom Zustand 0 zu 1 für den in Fig. 1 gezeigten Automaten zu unterscheiden. 4 in [2].

Um die Miles zu implementieren, können Sie natürlich das bekannte Verfahren zum Umschalten auf Moore-Maschinen verwenden. Dies führt jedoch zu einer Erhöhung der Anzahl von Zuständen und beseitigt die einfache, visuelle und nützliche Zuordnung von Modellzuständen zum Ergebnis ihrer Aktionen. Beispielsweise müssen nach solchen Transformationen die beiden Zustände des Moore-Automaten an den Einzelzustand der Ausgabe von Modell 1 aus [2] angepasst werden.

Bei einem komplexeren Modell treten Probleme mit Übergangsbedingungen offensichtlich auf. Um sie für die Software-Implementierung des betrachteten AND-NOT-Elements zu umgehen, wurde eine Analyse des Status der Eingangskanäle in den Modellsteuerungsdialog integriert, wie in Listing 5 gezeigt.

Listing 5. Dialogfeld zur Steuerung von NAND-Elementen
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "ine.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    pine = new ine(this);
    connect(this, SIGNAL(GoState0()), pine, SIGNAL(GoS0()));
    connect(this, SIGNAL(GoState1()), pine, SIGNAL(GoS1()));
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_checkBoxX1_clicked(bool checked)
{
    bX1 = checked;
    pine->bX1 = bX1;
    bY = !(bX1&&bX2);
    if (!(bX1&&bX2)) emit GoState0();
    else emit GoState1();
}

void MainWindow::on_checkBoxX2_clicked(bool checked)
{
    bX2 = checked;
    pine->bX2 = bX2;
    bY = !(bX1&&bX2);
    if (!(bX1&&bX2)) emit GoState0();
    else emit GoState1();
}


Zusammenfassend lässt sich sagen, dass all dies das Modell deutlich kompliziert und Probleme beim Verständnis seiner Arbeit verursacht. Darüber hinaus funktionieren solche lokalen Lösungen möglicherweise nicht, wenn Kompositionen aus ihnen berücksichtigt werden. Ein gutes Beispiel in diesem Fall wäre der Versuch, ein RS-Trigger-Modell zu erstellen (weitere Einzelheiten zu einem Zweikomponenten-Automatenmodell eines RS-Triggers finden Sie in [5]). Die Freude am erzielten Ergebnis wird jedoch den Fans von Event-Maschinen zuteil. Wenn ... es sei denn natürlich, sie haben Erfolg;)

5. Quadratische Funktion und ... Schmetterling?


Es ist zweckmäßig, die Eingabedaten als externen parallelen Prozess darzustellen. Darüber befand sich der Dialog zum Verwalten des Modells eines AND-NOT-Elements. Darüber hinaus kann die Änderungsrate der Daten das Ergebnis erheblich beeinflussen. Um dies zu bestätigen, betrachten wir die Berechnung der quadratischen Funktion y = ax² + bx + c, die wir in Form einer Menge parallel funktionierender und interagierender Blöcke implementieren.

Es ist bekannt, dass der Graph einer quadratischen Funktion die Form einer Parabel hat. Aber die vom Mathematiker dargestellte Parabel und die Parabel, die beispielsweise das Oszilloskop anzeigt, werden streng genommen niemals übereinstimmen. Der Grund dafür ist, dass der Mathematiker oft in augenblicklichen Kategorien denkt und glaubt, dass eine Änderung der Eingabemenge sofort zur Berechnung der Funktion führt. Aber im wirklichen Leben ist das überhaupt nicht wahr. Das Erscheinungsbild des Funktionsdiagramms hängt von der Geschwindigkeit des Rechners, der Änderungsrate der Eingabedaten usw. ab. usw. Ja, und die Programme selbst für die Geschwindigkeit können sich voneinander unterscheiden. Diese Faktoren beeinflussen die Form der Funktion, in deren Form es in einer bestimmten Situation schwierig sein wird, die Parabel zu erraten. Davon werden wir weiter überzeugt sein.

Also fügen wir dem Summierblock die Blöcke Multiplikation, Division und Exponentiation hinzu. Mit einer solchen Menge können wir mathematische Ausdrücke beliebiger Komplexität „sammeln“. Wir können aber auch „Cubes“ haben, die komplexere Funktionen implementieren. Abb. 5. Die Implementierung einer quadratischen Funktion wird in zwei Versionen gezeigt - Mehrfachblock (siehe Pfeil mit einer Beschriftung - 1 und auch Fig. 6) und eine Variante aus einem Block (in Fig. 5 Pfeil mit einer Beschriftung 2).

Bild
Abb.5. Zwei Möglichkeiten zur Implementierung der quadratischen Funktion

Bild
Abb. 6. Strukturmodell zur Berechnung einer quadratischen Funktion

Das in Abb. 5. Es scheint „verschwommen“ zu sein (siehe Pfeil mit der Markierung 3). Bei richtiger Vergrößerung (siehe Grafiken (Trends) mit der Markierung 4) ist man davon überzeugt, dass die Materie nicht in den Grafikeigenschaften liegt. Dies ist das Ergebnis des Einflusses der Zeit auf die Berechnung von Variablen: Die Variable y1 ist der Ausgabewert der Mehrblockvariante (rote Farbe des Diagramms) und die Variable y2 ist der Ausgabewert der Einzelblockvariante (schwarze Farbe). Diese beiden Grafiken unterscheiden sich jedoch von den "abstrakten Grafiken" [y2 (t), x (t-1)] (grün). Letzteres ist für den Wert der Variablen y2 und den um einen Taktzyklus verzögerten Wert der Eingangsvariablen konstruiert (siehe die Variable mit dem Namen x [t-1]).

Je höher die Änderungsrate der Eingabefunktion x (t) ist, desto stärker ist der "Unschärfeeffekt" und desto weiter sind die Graphen y1, y2 vom Graphen [y2 (t), x (t-1)] entfernt. Der erkannte "Defekt" kann für eigene Zwecke verwendet werden. Zum Beispiel hindert uns nichts daran, ein sinusförmiges Signal an den Eingang anzulegen. Wir werden eine noch kompliziertere Option in Betracht ziehen, wenn sich auch der erste Koeffizient der Gleichung auf ähnliche Weise ändert. Das Ergebnis des Experiments zeigt einen Bildschirm des VKPa-Mediums, der in Fig. 1 gezeigt ist. 7.

Bild
Abb. 7. Simulationsergebnisse mit einem sinusförmigen Eingangssignal

Der Bildschirm unten links zeigt das Signal, das an die Eingänge von Realisierungen einer quadratischen Funktion geliefert wird. Darüber befinden sich die Diagramme der Ausgabewerte y1 und y2. Diagramme in Form von „Flügeln“ sind Werte, die in zwei Koordinaten dargestellt sind. Mit Hilfe verschiedener Realisierungen der quadratischen Funktion haben wir also die Hälfte des „Schmetterlings“ gezeichnet. Ein Ganzes zu zeichnen ist eine Frage der Technologie ...

Aber die Paradoxien der Parallelität enden hier nicht. In Abb. Abbildung 8 zeigt Trends bei der „umgekehrten“ Änderung der unabhängigen Variablen x. Sie gehen bereits links vom "abstrakten" Graphen vorbei (letzterer hat, wie wir bemerken, seine Position nicht geändert!).

Bild
Feige. 8. Die Art der Graphen mit direkter und umgekehrter linearer Änderung des Eingangssignals

In diesem Beispiel wird der "doppelte" Fehler des Ausgangssignals in Bezug auf seinen "momentanen" Wert offensichtlich. Und je langsamer das Computersystem oder je höher die Signalfrequenz ist, desto größer ist der Fehler. Eine Sinuswelle ist ein Beispiel für eine Vorwärts- und Rückwärtsänderung eines Eingangssignals. Aus diesem Grund haben die „Flügel“ in Abb. 4 diese Form angenommen. Ohne den Effekt „Rückwärtsfehler“ wären sie doppelt so eng.

6. Adaptiver PID-Regler


Betrachten wir noch ein Beispiel, an dem die betrachteten Probleme gezeigt werden. In Abb. Abbildung 9 zeigt die Konfiguration des VKP (a) -Mediums bei der Modellierung eines adaptiven PID-Reglers. Es wird auch ein Blockdiagramm gezeigt, in dem der PID-Regler durch eine Einheit namens PID dargestellt wird. Auf der Black-Box-Ebene ähnelt es der zuvor betrachteten Einzelblock-Implementierung einer quadratischen Funktion.

Das Ergebnis des Vergleichs der Ergebnisse der Berechnung des PID-Reglermodells innerhalb eines bestimmten mathematischen Pakets und des aus den Simulationsergebnissen in der VKP (a) -Umgebung erhaltenen Protokolls ist in 10 gezeigt, wobei das rote Diagramm die berechneten Werte und das blaue Diagramm das Protokoll ist. Der Grund für ihre Nichtübereinstimmung besteht darin, dass die Berechnung im Rahmen des mathematischen Pakets, wie durch weitere Analyse gezeigt, der sequentiellen Operation der Objekte entspricht, wenn sie zuerst den PID-Regler und den Stopp ausarbeiten, und dann das Modell des Objekts usw. in der Schleife. Die VKPa-Umgebung implementiert / modelliert den Parallelbetrieb von Objekten entsprechend der realen Situation, wenn das Modell und das Objekt parallel arbeiten.

Bild
Abb. 9. Implementierung des PID-Reglers

Bild
Abb. 10. Vergleich der berechneten Werte mit den Simulationsergebnissen des PID-Reglers

Da es, wie bereits angekündigt, in VKP (a) einen Modus zur Simulation des sequentiellen Betriebs von Strukturblöcken gibt, ist es nicht schwierig, die Hypothese eines sequentiellen Berechnungsmodus des PID-Reglers zu überprüfen. Wenn Sie den Betriebsmodus des Mediums auf seriell ändern, erhalten Sie die Übereinstimmung der Diagramme, wie in Abb. 11 gezeigt.

Bild
Abb. 11. Sequenzieller Betrieb des PID-Reglers und des Steuerobjekts

7. Schlussfolgerung


Im Rahmen der KPdSU (a) verleiht das Rechenmodell Programmen Eigenschaften, die für reale "lebende" Objekte charakteristisch sind. Daher die bildliche Assoziation mit „lebendiger Mathematik“. Wie die Praxis zeigt, sind wir in Modellen einfach verpflichtet, das zu berücksichtigen, was im wirklichen Leben nicht ignoriert werden kann. Beim parallelen Rechnen ist dies hauptsächlich die Zeit und Endlichkeit der Berechnungen. Natürlich, ohne die Angemessenheit des [automatischen] mathematischen Modells für das eine oder andere „lebende“ Objekt zu vergessen.

Es ist unmöglich, das zu bekämpfen, was nicht besiegt werden kann. Es ist Zeit. Dies ist nur im Märchen möglich. Es ist jedoch sinnvoll, dies zu berücksichtigen und / oder sogar für eigene Zwecke zu verwenden. In der modernen parallelen Programmierung führt das Ignorieren der Zeit zu vielen schwer zu kontrollierenden und erkannten Problemen - Signalrennen, Deadlock-Prozesse, Probleme mit der Synchronisation usw. usw. Die VKP (a) -Technologie ist weitgehend frei von solchen Problemen, einfach weil sie ein Echtzeitmodell enthält und die Endlichkeit von Computerprozessen berücksichtigt. Es enthält, was die meisten seiner Analoga einfach ignoriert werden.

Abschließend. Schmetterlinge sind Schmetterlinge, aber Sie können beispielsweise ein Gleichungssystem für quadratische und lineare Funktionen betrachten. Dazu reicht es aus, dem bereits erstellten Modell ein lineares Funktionsmodell und einen Prozess hinzuzufügen, der deren Übereinstimmung steuert. Die Lösung wird also durch Modellierung gefunden. Es wird höchstwahrscheinlich nicht so genau wie analytisch sein, aber es wird einfacher und schneller erhalten. Und in vielen Fällen ist das mehr als genug. Und eine analytische Lösung zu finden, ist in der Regel oft eine offene Frage.
Im Zusammenhang mit letzterem wurden AVMs zurückgerufen. Für diejenigen, die nicht auf dem neuesten Stand sind oder vergessen haben, - analoge Computer. Strukturelle Prinzipien sind im Allgemeinen und der Ansatz, eine Lösung zu finden.

Anwendung


1) Video : youtu.be/vf9gNBAmOWQ

2) Archiv der Beispiele : github.com/lvs628/FsaHabr/blob/master/FsaHabr.zip .

3) Archiv der erforderlichen Qt-DLL-Bibliotheken Version 5.11.2 : github.com/lvs628/FsaHabr/blob/master/QtDLLs.zip

Beispiele werden in der Windows 7-Umgebung entwickelt. Um sie zu installieren, öffnen Sie das Beispielarchiv und wenn Sie Qt nicht installiert haben oder Die aktuelle Version von Qt unterscheidet sich von Version 5.11.2. Öffnen Sie dann zusätzlich das Qt-Archiv und schreiben Sie den Pfad in die Bibliotheken in der Umgebungsvariablen Path. Führen Sie als Nächstes \ FsaHabr \ VCPaMain \ release \ FsaHabr.exe aus und wählen Sie im Dialogfeld das Konfigurationsverzeichnis eines Beispiels aus, z. B. \ FsaHabr \ 9.ParallelOperators \ Heading1 \ Pict1.C2 = A + B + A1 + B1 \ (siehe auch Video).

Kommentar. Beim ersten Start wird anstelle des Verzeichnisauswahldialogs möglicherweise ein Dateiauswahldialog angezeigt. Wir wählen auch das Konfigurationsverzeichnis und einige darin enthaltene Dateien aus, z. B. vSetting.txt. Wenn der Konfigurationsauswahldialog überhaupt nicht angezeigt wird, löschen Sie vor dem Start die Datei ConfigFsaHabr.txt in dem Verzeichnis, in dem sich die Datei FsaHabr.exe befindet.

Um die Konfigurationsauswahl im Dialogfeld „Kernel: Automatische Leerzeichen“ nicht zu wiederholen (sie kann über den Menüpunkt FSA-Tools / Speicherverwaltung / Verwaltung geöffnet werden), klicken Sie auf die Schaltfläche „Verzeichnispfad speichern“ und deaktivieren Sie „Dialogfeld zur Konfigurationsauswahl beim Start anzeigen“ ". Um in Zukunft eine andere Konfiguration auszuwählen, muss diese Auswahl erneut festgelegt werden.

Literatur


1. NPS, Förderer, automatisches Rechnen und wieder ... Coroutinen. [Elektronische Ressource], Zugriffsmodus: habr.com/de/post/488808 kostenlos. Yaz. Russisch (Datum der Behandlung 22.02.2020).
2. Sind Automaten eine Ereignissache? [Elektronische Ressource], Zugriffsmodus: habr.com/de/post/483610 kostenlos. Yaz. Russisch (Datum der Behandlung 22.02.2020).
3. BUCH G., RAMBO J., JACOBSON I. UML. Handbuch. Zweite Ausgabe. IT-Akademie: Moskau, 2007 - 493 S.
4. Rogachev G.N. Stateflow V5. Handbuch. [Elektronische Ressource], Zugriffsmodus: bourabai.kz/cm/stateflow.htm kostenlos. Yaz. Russisch (Umlaufdatum 10.04.2020).
5.Paralleles Rechenmodell [Elektronische Ressource], Zugriffsmodus: habr.com/de/post/486622 kostenlos. Yaz. Russisch (Umlaufdatum 11.04.2020).

All Articles