Baustein mit INOUT oder Baustein mit Interface?????

Zuviel Werbung?
-> Hier kostenlos registrieren
Bei Übergabe eines Zeigers auf eine Struktur, wird in der Funktion nur einmalig ein Register wie ebx mit der Adresse geladen, und die folgenden Anweisungen laufen dann über sowas wie MOV eax, word ptr [ebx+0].
Wenn mehrere Strukturen über INOUT übergeben werden und schön gemischt zugegriffen wird, dann nützt das Halten der Anfangsadresse einer Struktur wohl eher wenig.

Harald
 
Wenn mehrere Strukturen über INOUT übergeben werden und schön gemischt zugegriffen wird, dann nützt das Halten der Anfangsadresse einer Struktur wohl eher wenig.
Es gibt aber mehr als nur ein Register. Zumindest wenn Daten zwischen zwei Strukturen ausgetauscht werden sollen, können dazu die Anfangsadressen in den Registern gehalten werden. Klar, irgendwann "kostet" es mehr als die einzelnen Werte zu übergeben, aber wer schreibt (und wartet) eine Funktion mit hunderten Funktionsparametern?

Außerdem erfolgt auch der Zugriff auf Funktionsparameter in ähnlicher Weise wie auf die Variablen einer Struktur, nämlich per Stack(Base-)pointer plus Offset. D.h. die Funktionsparameter entsprechen so gesehen auch einer Struktur, nur ist der Zeiger auf die Struktur der Funktionsparameter innerhalb einer Funktion im esp-Register gespeichert. Wenn du nur eine einizige Struktur übergibst, macht das darum kaum einen Unterschied.

Ich weiß aber nicht wie Codesys die In-Out Parameter in Assembler umsetzt. Wenn in C z.B. ein Parameter als Zeiger auf einen Int übergeben wird damit darüber Werte zurückgegeben werden können, muss beim Zugriff auf diesen auch erst die Adresse des Parameters (esp+offset) in ein Register geladen werden, um dann in der nächsten Anweisung indirekt darauf zugreifen zu können.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Wie der Zugriff auf die Übergabeparameter im Detail erfolgt ist für den Performance-Vergleich "Struktur-Übergabe per IN vs. INOUT" erstmal zweitrangig, weil der bei beiden Varianten ja gleich erfolgt. Fakt ist aber, daß
  • bei Übergabe per IN (byValue) liefert das Lesen des Übergabeparameters bereits den Wert der außen angeschalteten Variable
  • bei Übergabe per INOUT (byRef) liefert das Lesen des Übergabeparameters zunächst nur die Referenz auf die Variable
    --> es muß ein weiteres Mal auf den Speicher zugegriffen werden. Das geht unmöglich zusammen in nur 1 Takt. (dieser weitere Zugriff geht auf unvorhersehbare Adressen, meist in Speicher außerhalb des Stack)
Weil bei Siemens S7 bei byRef keine Adresse übergeben wird sondern ein zusammengesetzter POINTER, gestaltet sich der weitere Zugriff auf die referenzierte Variable relativ aufwendig. Ich habe keine Ahnung, ob bei Codesys eine Flat-Adresse übergeben wird oder vielleicht ebenfalls etwas ähnliches wie Bereichsselektor + Offset?

Ich kann mir vorstellen, daß die Übergabe von Referenzen via INOUT Performance-Vorteile beim Durchreichen durch mehrere Bausteinaufrufe hätte, falls das überhaupt geht (bei Siemens S7 geht es nicht), doch bei SPS-Programmen ist die Schachtelungstiefe meist nicht groß.

  • Für den Aufrufer ist es natürlich effizienter, einfach nur eine Referenz auf eine Stuktur zu übergeben, als die Struktur zu kopieren. Doch macht das den Mehraufwand beim Parameter-Lesen wett?

Für den Vergleich der Performance von Struktur-Übergabe per IN vs. INOUT habe ich ein Testprogramm in SCL erstellt:
Code:
TYPE Type1
  STRUCT
    a : REAL;
    b : REAL;
    c : REAL;
    d : REAL;
    e : REAL;
    f : REAL;
  END_STRUCT
END_TYPE

//*** aufgerufene Function ***********************
FUNCTION FC_foo : REAL
VAR_INPUT
[COLOR="#008000"]//VAR_IN_OUT[/COLOR]
  s1 : Type1;
  s2 : Type1;
END_VAR

  FC_foo:=(s1.a*s1.b+s2.a*s2.b) + (s1.c*s2.c+s1.d*s2.d) + (s1.e+s2.e)*2.0 - (s1.a*s1.f+s2.a*s2.f);
END_FUNCTION

//*** aufgerufener Function_Block ****************
FUNCTION_BLOCK FB_foo
VAR_INPUT
[COLOR="#008000"]//VAR_IN_OUT[/COLOR]
  s1 : Type1;
  s2 : Type1;
END_VAR
VAR_OUTPUT
  out : REAL;
END_VAR

  out:=(s1.a*s1.b+s2.a*s2.b) + (s1.c*s2.c+s1.d*s2.d) + (s1.e+s2.e)*2.0 - (s1.a*s1.f+s2.a*s2.f);
END_FUNCTION_BLOCK

//*** aufrufender FUNCTION_BLOCK *****************
FUNCTION_BLOCK FB_caller
VAR
  Result : REAL;
  S1 : Type1;
  Instanz1 : FB_foo;
  S2 : Type1;
  Instanz2 : FB_foo;
END_VAR

(* Aufruf FC ... *)
[COLOR="#008000"]//  Result:=FC_foo(s1:=S1, s2:=S2);[/COLOR]

(* ... oder FB *)
  Instanz2(s1:=S1, s2:=S2);
  Result:=Instanz2.out;
END_FUNCTION_BLOCK

Ergebnis mit SCL-Compiler V5.3 für S7-300/400:
Code:
Codegröße bei        FB_caller  FB_foo  | FB_caller  FC_foo
Übergabe VAR_INPUT  :   166      216    |    122      474
Übergabe VAR_IN_OUT :   106      436    |    122      474
Änderung Codegröße  :  - 60    + 220    |      0        0 (*)

(*) FC: Übergabe zusammengesetzter Datentypen erfolgt immer per POINTER

Man sieht, daß bei S7 die Codegröße (und ziemlich wahrscheinlich auch die Ausführungszeit) bei Übergabe per VAR_IN_OUT insgesamt ansteigt - also die Performance schlechter als bei Übergabe per VAR_INPUT ist.
(dabei benutzt der SCL-Compiler noch nichtmal BLKMOV oder memcpy, um die Strukturen in VAR_INPUT zu kopieren, sondern kopiert in 4-Byte-Häppchen)

Jetzt interessiert mich natürlich, wie das auf einem Codesys-System aussieht. Leider habe ich kein Codesys zur Verfügung. Vielleicht kann mal jemand den Vergleich machen und berichten?

Interessant wäre auch wie der Vergleich für die S7-1500 und die S7-1200 mit "optimiertem" und "Standard"-Speicherzugriff ausfällt.



"Nebenkriegsschauplatz":
Wegen der mehrfachen Verwendung von s1.a und s2.a in FC_foo und FB_foo MÜSSTEN diese Werte bei Übergabe per Referenz vor Verwendung zuerst lokal/temporär zwischengespeichert werden um Multitasking-Konsistenzprobleme zu vermeiden!
Z.B. ein Regler in der OB35-Task schreibt direkt in eine globale Struktur (in S1.a oder S2.a), welche in der OB1-Task an einen FC oder per IN_OUT an FB übergeben wird --> wenn der OB35 den FC/FB unterbricht, dann kann es zu fehlerhaften Berechnungen kommen, weil mit Werten von vor der Unterbrechung und eventuell geänderten Werten von nach der Unterbrechung gerechnet wird. Der SCL-Compiler berücksichtigt dieses Problem nicht, man muß selber dafür sorgen, daß nicht in verschiedenen Task auf die Struktur geschrieben wird. Ggf. muß man selber zuerst für das Umkopieren auf lokalen Speicher sorgen.

Harald
 
Ich kann morgen mal bei Codesys nachsehen wie In-Out umgesetzt wird, hab zwar schon ein paar fertige "Assemblate" aber nichts mit In-Out Parametern

Ich persönlich würde mir bei Codesys darüber so gut wie keine Gedanken machen.
Außer bei Siemens S7 verwende ich eine Struktur wenn ich eine Struktur benötige, einen Parameter per Value wenn ich es benötige, und einen Zeiger ich einen Zeiger benötige. Nur bei Siemens programmiert man nicht wie es am elegantesten ist, sondern damit es dieses kaputte Siemens-Konzept aus S5-Zeiten, die langsamen CPUs und in den beschnitteten Speicher passt. Und dieses Prinzip wurde bei der 1200/1500 weitergeführt, wo alles ohne triftigen Grund irgendwie anders sein muss, und wenn man nicht aufpasst anstatt mit einem ICE mit der Bimmelbahn unterwegs ist.

Wie heißt es so schön von Donalt Knuth: "Premature optimization is the root of all evil".
Solange ich in der SPS kein Numbercrunching veranstalten will, programmiere ich so wie es am elegantesten ist....außer bei Siemens eben.
 
... das ist möglicherweise dem Umstand geschuldet, dass bei CodeSys und Konsorten eventuell nur noch mit Referenzen gearbeitet wird.
In dem Fall würde eine Struktur tatsächlich für den Rechner keinen Mehr-Aufwand bedeuten ...

Gruß
Larry
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Dann wollen wir doch mal ein bisschen Licht ins Dunkel bringen:

Ein VAR_IN_OUT ist in CODESYS einfach realisiert als POINTER auf die übergebenen Daten. Der Zugriff darauf benötigt eine Dereferenzierung
mehr als der direkte Zugriff.
Bei VAR_INPUT wird der Wert kopiert.
Will man unabsichtliches verändern des übergebenen Wertes verhindern, dann kann man VAR_IN_OUT CONSTANT verwenden
(seit Version 3.5.2.0). Auf so eine Variable darf nicht geschrieben werden.

Wenn man eine Struktur (oder FB oder STRING) an eine Funktion (oder FB) übergibt, dann ist es in aller Regel sinnvoll, das per VAR_IN_OUT zu tun.
Der Aufruf geht viel schneller und der Zugriff auf die Struktur per VAR_IN_OUT ist kaum langsamer.
Allenfalls wenn immer alle Werte einer Struktur verarbeitet werden und vielleicht sogar mehrfach darauf zugegriffen wird
könnte es vorteilhaft sein die Struktur zu kopieren. Der Speicherverbrauch ist in jedem Fall höher.

Ein Interface ist vermutlich nicht sinnvoll, wenn man nur den Zugriff auf Daten bieten will. Der Zugriff auf eine Eigenschaft eines Interfaces
entspricht immer einem Funktionsaufruf und ist dementsprechend deutlich langsamer als der Zugriff auf ein Strukturelement.
Interfaces sollte man dann verwenden wenn man Code schreibt, der verschieden implementierte FB's verwendet, die gemeinsame Eigenschaften haben.
Ein Beispiel habe ich schon mal in diesem Thread beschrieben:

http://www.sps-forum.de/codesys-und-iec61131/69842-codesys-objektorientiertes-programmieren.html


Ich finde es vor allem dann sehr sinnvoll, mit Interfaces zu arbeiten, wenn man Bibliotheken schreibt.
Viele unserer Kunden entwickeln ganze Bibliothekslandschaften und da kann es sehr sinnvoll sein, dass die Bibliotheken mit gemeinsamen
Interfaces arbeiten.
Überhaupt muss man feststellen dass sich die ganze Power der Objektorientierung an einem einzelnen Projekt oft nicht gut darstellen lässt.
Die Vorteile kommen zum Tragen wenn man weiterentwickelt, Varianten produziert, standardisierte Abläufe hat, wenn Teams an Projekten
arbeiten und wenn Teams an Bibliotheken arbeiten.
 
Wobei ob schneller oder langsamer ist vermutlich nicht die Frage, sondern eigentlich was ist schöner.

Beim Thema Geschwindigkeit ist nur wichtig: Sobald man anfängt mit objektorientierter Programmierung muss man aufpassen, dass Variablen die nicht statisch angelegt sind sondern am Stack liegen möglichst zu vermeiden.

Beispiel: Du hast einen Baustein mit seinen Eigenschaften (im VAR Bereich des FBs) und zugehörige Methoden, welche diese Eigenschaften für irgendwelche Abläufe verwenden. dann kann es passieren dass diese Methoden zyklisch aufgerufen werden. Wenn du nun im VAR Bereich der Methode einen String (zb. für eine Fehlermeldung) anlegst, dann wird diese Variable mit jedem Aufruf am Stack angelegt, mit 0 gelöscht und anschließend verwendet. Das sind dann bei einem STRING(200) mit jedem Durchlauf 200 Bytes löschen. Früher war diese Fehlervariable im statischen Bereich des Bausteins und somit gabs kein Problem.

Wir haben uns intern in der Firma zusammengetan und für den Umstieg auf objektorientiertes Programmieren ein paar Bücher zu Gemüte geführt. Wenn man sich nun an die üblich propagierten Patterns hält kann man hier enorm profitieren.

Als Beispiel haben wir uns einen Logging Mechanismus gebaut => hier wurde die Art und weiße wie Meldungen abgespeichert und exportiert werden komplett über Dependency Injection Pattern getrennt. Das heißt die Meldungen werden von einem Baustein abgespeichert (der schaut für den Programmierer immer gleich aus) und dieser bekommt einen anderen "eingepflanzt" welcher die Daten exportiert. Wobei hier kann gewählt werden ob in eine Datei oder in eine Datenbank oder über die serielle Schnittstelle raus exportiert wird.

Falls hier nicht das nötige KnowHow vorhanden ist, sollte man beim Umstieg evtl. auf einen Software Architekten hören, da spart man sich viel Ärger!

lg seehma
 
Wenn du nun im VAR Bereich der Methode einen String (zb. für eine Fehlermeldung) anlegst, dann wird diese Variable mit jedem Aufruf am Stack angelegt, mit 0 gelöscht und anschließend verwendet. Das sind dann bei einem STRING(200) mit jedem Durchlauf 200 Bytes löschen.
Hast du nachgesehen ob das wirklich so umgesetzt ist?
Dann müsste im IEC-Standard definiert sein, dass alle Bytes genullt werden müssen. Ist das dort nicht festgelegt, würde ich persönlich - wäre ich Compilerbauer - nur die Aktuallänge auf Null setzen.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Als Zusatz:
Bei einem FB ist es gar nicht möglich bei jedem Aufruf die Werte auf Null zu setzen, denn es ist ja gerade die Eigenschaft eines FB dass die Werte im Var-Bereich statisch sind. Also wenn da etwas genullt wird, dann nur einmalig.
 
Also im Standard hab ich nicht nachgesehen (hab den leider nicht, bekommt man den woher?), aber wir haben es gemessen und sind dann in der Doku auf das 'no initialize' attribut gekommen. Bei einem FB (jetzt Object) im VAR Bereich wird nur beim Start einer SPS alles mit 0 initialisiert (static eben).

Anfangs hatten wir das Problem mit der Initialisierung massiv. Wenn man ein paar hundert Methoden hat und in jeder werden am Stack solche Fehler-Strings angelegt, löscht man zyklisch 20000Bytes -> das dauert.

Wie gesagt bei kleineren Projekten (je nach notwendiger Zykluszeit) kein Problem. Wenn es mal ein paar tausend Methoden sind dann kann dies schnell zu einem Laufzeit Problem führen.

Lg
 
Jetzt interessiert mich natürlich, wie das auf einem Codesys-System aussieht. Leider habe ich kein Codesys zur Verfügung. Vielleicht kann mal jemand den Vergleich machen und berichten?
Findet sich denn keiner, der in der Lage ist, den Test mit einem Codesys-System zu machen?
Oder sind Codesys-Anwender zufrieden mit dem "Gefühl" von Performance statt mit Fakten?

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Natürlich sind wir nicht mit einem Gefühl zufrieden, allerdings konnte ich bisher nur bei einer Anlage die Grenzen des Systems ausloten, sonst hatten wir noch nie gröbere Probleme.

Wir verwenden Beckhoff (zugegeben, nicht das Standard Codesys System) Twincat 3.1 mit 64Bit und allen OO Features die es bietet.
Hauptsächlich sind auf Grund der Standardisierung bei uns 2 verschiedene IPCs im Einsatz einmal mit i5 (zwei echte Kerne) und einmal mit i7 (vier echte Kerne).
Einer dieser Kerne steht immer explizit der SPS Laufzeitumgebung zur Verfügung.

Durchschnittlich fahren wir mit einer Zykluszeit von 2ms, allerdings wären auch locker 1ms oder darunter möglich. Schneller als 500us bzw 250us macht eigentlich keinen Sinn mehr da die GFs der Filter der allermeisten I/O Klemmen schon bei ca. 1kHz liegen. Antriebe kommen bei diesen niedrigen Zykluszeiten auch schon ins schnaufen.

Bei einer Anlage haben wir ca. 122 Servoachsen verbaut (natürlich verwenden wir die ML des Regelgeräts) und ein paar tausend IO Punkte. Auch das ist für den i7 Rechner kein Problem. Bei 2ms Zyklus landen wir ungefähr (unter Vollast) bei 50% Auslastung (7 Ethercat Stränge).

Ich kann dir natürlich genauere Zahlen liefern, allerdings bräuchte ich hier einen allgemein gültigen, standardisierten Benchmark. Gibt es sowas in der PLC-Welt? Ich komme ja eigentlich von der Linux-C-Welt und da hatte wir in Punkto Performance nie Probleme uns wurde nur die Basisentwicklung zuviel.
 
Zurück
Oben