Step 7 Optimierung Schleife mit Pointern, indirekter, bereichsübergreifender Zugriff

Need4Speed

Level-2
Beiträge
32
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Forum,


("long time lurker, first time poster" - wie man so sagt)


ich habe es hier mit einer 315-2 PN/DP zu tun, bei der mein FB zum Datenschubsen mehrere ms Zykluszeit kostet (zwischen 1ms und 5ms, abhängig davon, wieviele Daten geschoben werden). Es gilt die Werte an den Adressen in einer Liste (wild gemischt: E, A, M, DB; SFC20/BLKMOV bringt hier nix) möglichst effizient (= schnell) in einen zusammenhängenden Bereich zu transferieren.


Mein aktueller Ansatz sieht so aus (kostet ca. 5ms für 1000 bytes):

(Vorbereitung auf die Schleife)

Code:
          AddressPointers  :  ARRAY [0..5999] OF BYTE; // 1000x: DWORD for pointer, WORD for DBnr
          xAddressPointer  : DWORD;
          ...
          // fülle AddressPointers mit 1000 Adressen im Format L W#16#81000000; L 10; SLD 3; L W#16#82000000; L 42; SLD 3; ...
          ...


          LAR1     P##AddressPointers;
          // Zeiger auf Anfang des Bereichs, an dem die gewünschten Adressen als DWORD (pointer) + WORD (DBnr) stehen
          TAR1     xAddressPointer;            


          LAR1     P##OutputData;
          TAR1     xOutputPointer;             // Zeiger auf Anfang des Zieldatenbereichs
          L       DNIO;
          T       xDINOStore;        // rette DI Nummer

(die Schleife)
Code:
          L        1000; // z.B. 1k Durchläufe (1000 Bytes)
schl:     T        xLoopCounter;
          L        W [AR1,P#4.0];           // DB Nummer ist WORD 
          T        xDBNOStore;
          AUF      DB [xDBNOStore];
          L        D [AR1,P#0.0];                 // Lade Zeiger im DWORD Format
          AUF      DI [xDINOStore];              // DI Nummer wieder herstellen
          LAR1;                                 // DWORD Zeiger zu Adresse konvertieren
          L        B [AR1,P#0.0];               // Wert an der Adresse im ARR1 in AKKU1 laden
          LAR1     xOutputPointer;              //  Ausgabeadresse in ARR1 laden
          T        B [AR1,P#0.0];               // aktuellen Wert in AKKU1 in ARR1
          +AR1     P#1.0;                        // Ausgabeadresse in ARR1 um Länge von einem Byte weiter
          TAR1     xOutputPointer;              // Ausgabeadresse retten
          LAR1     xAddressPointer;            // aktuelle Startadresse der Zeigerdefinitionen laden
          +AR1     P#6.0;                           // aktuelle Startadresse der Zeigerdefinitionen laden um DWORD + WORD weiter schieben
          TAR1     xAddressPointer;            // aktuelle Startadresse der Zeigerdefinitionen retten
          L        xLoopCounter;
          LOOP     schl;




Da würde ich jetzt gerne so viele wie möglich Anweisungen einsparen - weiss aber nicht so recht, wie ich das gut und sicher tun kann. Ein Idee wäre "ARR2" auch zu verwenden, dann könnte ich im ARR1 nur die Zeiger zu den Eingangsdaten, und den Zeiger auf den Zieldatenbereich mit +AR2 schieben. Dass würde mir dann einige der LAR1 und TAR1 Anweisungen sparen. Allerdings weiss ich nicht, ob das wirklich sicher ist zu tun; in der Hilfe und im AWL Editor wird ja immer gewarnt, dass "+AR2" schlimme Nebeneffekte haben kann, und z.B. Anweisungen wie "L P#DB123.DBB 5" ggf. wiederum den AR2 verändern.




Was auch noch klasse wäre, wenn man sowas machen könnte; dass würde mir dann die Anweisungen ersparen, in ARR1 laden, retten, und wieder laden


Code:
L      W#16#83000000; // Maske für %M
L      100; // soll man %M 100.0 werden
SLD  3;
OD;
T      xZeiger;
L      MB [#xZeiger]; // Geht
L      B [#xZeiger];   // Geht NICHT; warum??? W#16#83 (die Info, dass es ein Merker ist) steht doch auch in xZeiger




Ich würde mich sehr über Eure Ideen und Anregungen (oder Lösungen :p) freuen!!


VG, Jürgen
 
Zuletzt bearbeitet:
Hi. Ganz verstanden was du vorhast habe ich nicht, aber...

Das Adressregister 2 kann man, wenn man einige Dinge beachtet problemlos bearbeiten.

In einem FC kannst du es immer verwenden, in einem FB muss zu Bausteinbeginn das AR2 mittels TAR2 gesichert werden und vor jeden symbolischen Zugriff auf die Instanzdaten (IN/OUT/STAT) sowie am Bausteinende wieder hergestellt werden da der Multiinstanzoffset darin gespeichert ist.

Bei den Befehlen +AR1/2 muss man aufpassen. Es sind 16 Bit Operationen die nur bis Byte 4095 (oder so) funktionieren. Danach fallen die Benötigten Bits aus dem Word.

Zu deiner letzen Frage. Bereichsübergreifende Adressierung funktioniert nur registerindirekt. Du verwendest speicherindirekt.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ist Deine Adressen-Tabelle AddressPointers und Dein OutputData im STAT des FB? Dann kann man noch ein klein wenig optimieren, muß allerdings zu den Adressen im STAT für die Pointer noch den Multiinstanzoffset aus dem AR2 addieren.

Für Dein Kopieren brauchst Du 3 variable Pointer, wovon das Laden des Byte aus der in der Adressen-Tabelle angegebenen Adresse effizient leider nur registerindirekt geht, weil bereichsübergreifende indirekte Adressierung nicht speicherindirekt geht. Deshalb muß mindestens ein AR-Register-Wert in Arbeitspeicher temporär zwischengespeichert werden. Speicherindirekte Adressierung zur Vermeidung von AR-Umladen ist nur beim Laden des Bytes interessant - da aber, wie bereits gesagt, nicht möglich.

Ich habe hier mal 4 verschiedene Varianten der Schleife. Welche davon am schnellsten ist probiere bitte selber aus.
Code:
FUNCTION_BLOCK FBxxx
VAR
  AddressPointers : ARRAY  [0 .. 999] OF STRUCT
   DBno : WORD ;                      //POINTER geht nicht in STAT
   Addr : DWORD ;                     //deshalb ein STRUCT wie POINTER
  END_STRUCT ;
  OutputData : ARRAY  [0 .. 999] OF BYTE ;
END_VAR
VAR_TEMP
  SourceDB : WORD ;
  pSourcePointer : DWORD ;
  DestDB : WORD ;
  pDestPointer : DWORD ;
  iLoopCounter : INT ;
  AR2_Save : DWORD ;
END_VAR
Code:
//Adresspointers füllen
//Pointer DB5.DBX3.0
      L     5; //DB5
      T     #AddressPointers[0].DBno;
      L     P#DBX 3.0;
      T     #AddressPointers[0].Addr;
//...

//pSourcePointer auf Pointer-Tabelle AddressPointers[0] (in STAT) setzen:
      TAR2  ;                    //Multiinstanzoffset aus AR2 (Bereichskennung DB)
      UD    DW#16#FFFFFF;        //Bereichskennung entfernen
      L     P##AddressPointers;  //Adresse der Pointer-Tabelle in dieser Instanz (Bereichskennung DI)
      +D    ;
      T     #pSourcePointer;     //Adresse #AddressPointers[0] im IDB

//pDestPointer auf Ausgabebereich OutputData[0] (in STAT) setzen:
      TAR2  ;                    //Multiinstanzoffset aus AR2 (Bereichskennung DB)
      UD    DW#16#FFFFFF;        //Bereichskennung entfernen
      L     P##OutputData;       //Adresse des Ausgabe-Array in dieser Instanz (Bereichskennung DI)
      +D    ;
      T     #pDestPointer;       //Adresse #OutputData[0] im IDB

Und nun 4 Varianten der Schleife:
Code:
      L     1000;
lop1: T     #iLoopCounter;

//   QuellAdresse aus Pointer-Tabelle in STAT lesen
      L     DIW [#pSourcePointer]; //#AddressPointers[i].DBno
      T     #SourceDB;
      AUF   DB [#SourceDB];

      L     #pSourcePointer;       //SourcePointer weiterstellen auf #AddressPointers[i].Addr
      +     L#16;                  //+ P#2.0
      T     #pSourcePointer;
      L     DID [#pSourcePointer]; //#AddressPointers[i].Addr
      LAR1  ;                      //DBNO+AR1 zeigt nun auf die Adresse aus der Tabelle 

      L     #pSourcePointer;       //SourcePointer weiterstellen auf #AddressPointers[i+1]
      +     L#32;                  //+ P#4.0
      T     #pSourcePointer;       //und merken

//   Byte von der Adresse in Ausgabespeicherbereich kopieren
      L     B [AR1,P#0.0];         //Byte von der QuellAdresse lesen
      LAR1  #pDestPointer;         //Ziel-Pointer
      T     DIB [AR1,P#0.0];       //Byte in Ausgabespeicher speichern

      +AR1  P#1.0;                 //Ausgabepointer auf nächstes Byte stellen
      TAR1  #pDestPointer;         //nächste Ausgabeadresse merken

      L     #iLoopCounter;
      LOOP  lop1;
Code:
      L     1000;
lop2: T     #iLoopCounter;

//   QuellAdresse aus Pointer-Tabelle in STAT lesen
      LAR1  #pSourcePointer;
      L     DIW [AR1,P#0.0];       //#AddressPointers[i].DBno
      T     #SourceDB;
      AUF   DB [#SourceDB];

      L     DID [AR1,P#2.0];       //#AddressPointers[i].Addr
      +AR1  P#6.0;                 //SourcePointer weiterstellen auf #AddressPointers[i+1]
      TAR1  #pSourcePointer;       //und merken
      LAR1  ;                      //DBNO+AR1 zeigt nun auf die Adresse aus der Tabelle 

//   Byte von der Adresse in Ausgabespeicherbereich kopieren
      L     B [AR1,P#0.0];         //Byte von der QuellAdresse lesen
      LAR1  #pDestPointer;         //Ziel-Pointer
      T     DIB [AR1,P#0.0];       //Byte in Ausgabespeicher speichern

      +AR1  P#1.0;                 //Ausgabepointer auf nächstes Byte stellen
      TAR1  #pDestPointer;         //nächste Ausgabeadresse merken

      L     #iLoopCounter;
      LOOP  lop2;
Code:
      TAR2  #AR2_Save;
      LAR2  #pDestPointer;
//ab hier kann nicht symbolisch auf IDB-Instanzvariablen zugegriffen werden!

      L     1000;
lop3: T     #iLoopCounter;

//   QuellAdresse aus Pointer-Tabelle in STAT lesen
      LAR1  #pSourcePointer;
      L     DIW [AR1,P#0.0];       //#AddressPointers[i].DBno
      T     #SourceDB;
      AUF   DB [#SourceDB];

      L     DID [AR1,P#2.0];       //#AddressPointers[i].Addr
      +AR1  P#6.0;                 //SourcePointer weiterstellen auf #AddressPointers[i+1]
      TAR1  #pSourcePointer;       //und merken
      LAR1  ;                      //DBNO+AR1 zeigt nun auf die Adresse aus der Tabelle 

//   Byte von der Adresse in Ausgabespeicherbereich kopieren
      L     B [AR1,P#0.0];         //Byte von der QuellAdresse lesen
      T     DIB [AR2,P#0.0];       //Byte in Ausgabespeicher speichern

      +AR2  P#1.0;                 //Ausgabepointer auf nächstes Byte stellen

      L     #iLoopCounter;
      LOOP  lop3;

      LAR2  #AR2_Save;             //AR2 wiederherstellen
//ab hier symbolischer Zugriff auf IDB-Instanzvariablen wieder möglich
Code:
      TAR2  #AR2_Save;
      LAR1  #pSourcePointer;
//in der Schleife kann nicht symbolisch auf IDB-Instanzvariablen zugegriffen werden!

      L     1000;
lop4: T     #iLoopCounter;

//   QuellAdresse aus Pointer-Tabelle in STAT lesen
      L     DIW [AR1,P#0.0];       //#AddressPointers[i].DBno
      T     #SourceDB;
      AUF   DB [#SourceDB];

      L     DID [AR1,P#2.0];       //#AddressPointers[i].Addr
      +AR1  P#6.0;                 //SourcePointer weiterstellen auf #AddressPointers[i+1]
      LAR2  ;                      //DBNO+AR2 zeigt nun auf die Adresse aus der Tabelle 

//   Byte von der Adresse in Ausgabespeicherbereich kopieren
      L     B [AR2,P#0.0];         //Byte von der QuellAdresse lesen
      T     DIB [#pDestPointer];   //Byte in Ausgabespeicher speichern

      LAR2  #pDestPointer;         //Ausgabepointer
      +AR2  P#1.0;                 //auf nächstes Byte stellen
      TAR2  #pDestPointer;         //und merken

      L     #iLoopCounter;
      LOOP  lop4;

      LAR2  #AR2_Save;             //AR2 wiederherstellen
//ab hier symbolischer Zugriff auf IDB-Instanzvariablen wieder möglich

Harald
 
Zuletzt bearbeitet:
Bei den Befehlen +AR1/2 muss man aufpassen. Es sind 16 Bit Operationen die nur bis Byte 4095 (oder so) funktionieren. Danach fallen die Benötigten Bits aus dem Word.
Das ist so nicht ganz richtig. +AR1/+AR2 sind 24-Bit-Operationen/Additionen, allerdings ist der Operand nur ein 16-Bit-Wert, welcher vor der Addition vorzeichenrichtig auf 24 Bit erweitert wird, wodurch man maximal P#4095.7 zum Wert in ARx addieren kann:
Code:
LAR1 P#50000.0
+AR1 P#4095.7 //ergibt AR1 = P#54095.7

Harald
 
Hallo Harald,

vielen herzlichen Dank für deine Hilfe- echt Killer!! Ich habe die Zeit jetzt so (für die reine Schleife) gemessen:

Code:
      CALL    SFC64                 //SFC 64 "TIME_TCK" (Systemzeit auslesen)
            (RET_VAL := #dTimestampStart);    


      CALL    SFC64                 //SFC 64 "TIME_TCK" (Systemzeit auslesen)
            (RET_VAL := #dTimestampEnde);


Bei allen vier Schleifen "zappelt" die Zeitmessung zwischen 2ms und 3ms - dann kann man definitiv jedenfalls keinen klaren Gewinner feststellen.
Tendenziell - aber das ist jetzt recht subjektiv (Variablentabelle beobachten) - scheint die "lop4" überwiegend bei 2ms zu liegen.
Alle Ansätze sehen sehr, sehr elegant und sauber aus, viel besser als meine Lösung (diese liegt übrigens bei zwischen 3ms und 4ms) - ich denke, dass ich jetzt mit der lp4 weiter mache.

Nochmals, vielen Dank!!!!

VG, Jürgen


P.S. in "lop2" und "lop3" ist noch ein kleiner Tippfehler:

Code:
     L     DID [AR1,P#2.0];       //#AddressPointers[i].Addr
     +AR1  P#6.0;                //SourcePointer weiterstellen auf #AddressPointers[i+1]
     T     #pSourcePointer;      //und merken

Da sollte die Zeile T #pSourcePointer; //und merken so sein TAR1 #pSourcePointer; //und merken
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
P.S. in "lop2" und "lop3" ist noch ein kleiner Tippfehler:
[...]
Da sollte die Zeile T #pSourcePointer; //und merken so sein TAR1 #pSourcePointer; //und merken
Du hast recht. Danke. Ich hatte die Varianten nicht getestet.
Ich habe es nun oben korrigiert.

lop3 und lop4 sollten die gleiche Ausführungszeit benötigen.
Bei lop3 könnte ein Rückübersetzen aus dem online-Programm bei Verlust des Quellcodes bei "T DIB [AR2..." Probleme machen, ich meine da kommt auch eine diesbezügliche Warnung bei der Übersetzung der AWL-Quelle.
Mir persönlich gefällt die lop2 am besten weil sie ohne AR2 auskommt. (AR2 enthält bei FB den Multiinstanzoffset und darf vor symbolischen Zugriffen auf Instanzvariablen nicht verändert sein)

Harald
 
Zuletzt bearbeitet:
Zurück
Oben