Time Cycle Control

Für die Annahme, dass gewünscht wird, sequentiell ablaufende Vorgänge mit Einzelschritten unterschiedlicher Zeitspanne unter verschiedenen Rahmenbedingungen möglichst gleichartig zu reproduzieren, wurde folgendes Konzept (mit Visual C++) realisiert:
(selbstverständlich ist die beschriebene Vorgangsweise auch auf andere Systeme übertragbar)

Definition der Chronologie:

Auf der Zeitachse werden Einzeloperationspakete in Einzelschritten zusammengefasst und einer Zeitspanne (in Millisekunden) zugeordnet.
Dies erfolgt in einer Pseudocode-Datenstruktur oder in einer den Erfordernissen angepassten Makrosprache:

SCHRITT_00_  OPERATION_01
SCHRITT_00_  OPERATION_02
ZEITKONTROLLPUNKT_01_MILLISEKUNDEN 233
SCHRITT_01_  OPERATION_01
SCHRITT_01_  OPERATION_02
SCHRITT_01_  OPERATION_03
ZEITKONTROLLPUNKT_02_MILLISEKUNDEN 815
SCHRITT_02_  OPERATION_01
SCHRITT_02_  OPERATION_02
ZEITKONTROLLPUNKT_03_MILLISEKUNDEN 766
[...]

Aus der Sicht desjenigen, der die Datenstruktur bzw. den Makrocode erstellt,
hat das angeführte Beispiel folgende Bedeutung:

SCHRITT_00 umfasst vorlaufende Aktionen, bevor die Zeitkontrolle einsetzt.
Damit wird festgelegt, dass für die Einzeloperationen des SCHRITT_01 minimal 233 Millisekunden zur Verfügung stehen.

Anwendung:

Unter entsprechender Zeitkontrolle (durch Summieren der theoretischen Einzelzeiten und dem Vergleich mit der tatsächlichen Zeitspanne zum ZEITKONTROLLPUNKT_01) ist zu jedem ZEITKONTROLLPUNKT bekannt, ob die "korrekte" Definition eingehalten ist, und damit bis zum theoretisch ermittelten Startpunkt der Operationen des nächsten ZEITKONTROLLPUNKT gewartet werden soll (Zeitschema eingehalten), oder ob sofort mit den anstehenden Operationen fortzusetzen ist (nachlaufen).
Es gibt also zu jedem ZEITKONTROLLPUNKT
- eine theoretische Zeit (Summe aller vorangegangenen Zeitspannen seit dem ZEITKONTROLLPUNKT_01)
- eine tatsächliche Zeit (betriebssystemabhängiger Messwert seit dem Betreten von ZEITKONTROLLPUNKT_01)

Umsetzung (Zeitmessung):

Windows-Ermittlung von Zeitspannen in Millisekunden:
Die Funktion


static DWORD rizTickCount()
{
    static double ticksPerMsec = -1.0l;
    static LONGLONG freq = (LONGLONG) 0;
    if(ticksPerMsec == -1.0l)
    {
        QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
        ticksPerMsec = (freq) ? ((DB)freq/(DB)1000.0) : 0.0;
    }
    if(ticksPerMsec)
    {
        LONGLONG x;
        QueryPerformanceCounter((LARGE_INTEGER*)&x);
        if(x) return(DWORD)(x/ticksPerMsec); 
    }
    return ::GetTickCount();
}
zeigt folgendes Ergebnis im Vergleich
zur Library-Funktion: GetTickCount();
(Zwischen den Aufrufen befindet sich jeweils ein Sleep(1);)

Windows XP Home Edition Version 2002 Service Pack 2
1.46GHz, 0.448GB RAM 1.60GHz, 0.99GB RAM
GetTickCount rizTickCount GetTickCount rizTickCount
Cycle absolute relative absolute relative Cycle absolute relative absolute relative
0 287462650 287660850 0 297619370 297631290
1 287462650 287660861 1 2976195316 2976314011
2 287462650 287660883 2 2976196831 2976315526
3 287462650 287660894 3 2976198447 2976317142
4 287462650 287660916 4 2976200063 2976318657
5 2874628116287660938 5 2976201578 2976320273
6 287462811628766095106 2976203194 2976321889
7 287462811628766097127 2976204610929763233104
8 287462811628766099148 2976206212529763249120
9 287462811628766101169 2976207814129763265136
1028746281162876610318102976209315629763280151
1128746281162876610520112976210917229763296167
1228746281162876610722122976212518829763311182
1328746296312876610924132976214020329763327198
1428746296312876611126142976215621929763343214
1528746296312876611328152976217123429763358229
1628746296312876611530162976218725029763374245

Beim linken Rechner ist die Ungenauigkeit von GetTickCount() sehr auffällig,
beim rechten wird dies abgeschwächt durch die Ungenauigkeit des Sleep(1); Versionskompatibilität:
Die Routine nimmt automatisch ::GetTickCount();,
falls die besseren Library-Funktionen nicht verfügbar sind.

Umsetzung (Zeitkontrolle):

Das Hauptprogramm, das die Datenstruktur bzw. Makrosprache abarbeitet, startet einen Thread (asynchron ablaufende Task mit gemeinsamen Daten) für die Abwicklung der Zeitkontrolle. (Damit die Threadfunktion innerhalb der Zeitkontroll-klasse Tim:: liegen kann, muss sie static deklariert sein.)

int Tim::macTimThread(MYTHREADPARAMS*threadParams)
{
    bool started = false, first = true; 
    DWORD init = 0, exit = 0, sys = 0, cycle = 0, delta = 0, rmAct = 0;
    while(!exitThread)
    {
        int sleep = 0; PROTOTYP typ = INAC; 
        Sleep(1);
        if(threadParams->threadFunction)
            (*(threadParams->threadFunction))();
        if(activationCtrl<0)
        {
            Sleep(threadParams->cycleMsecs);
            started = false;
            init = rizTickCount();
        } else {
            typ = IDLE;
            sys = rizTickCount();
            if(!started&&activationCtrl>0)
            {
                typ = INIT;
                if(first)
                { 
                    typ = FRST;
                    first = false; 
                    init = cycle = sys; 
                } else { 
                    delta = sys - cycle;
                    cycle = sys;
                }
                exit = init + activationCtrl;
                started = true;
            }
            if(activationCtrl)
            {
                sleep = exit - sys;
                if(typ == IDLE)
                    typ = SLEP;
                if(sleep>2)
                    Sleep(sleep/2);
                else { 
                    rmAct = activationCtrl;
                    activationCtrl = 0;
                    init = exit;
                    started = false;
                    if(typ == IDLE || typ == SLEP) 
                        typ = RINI; 
                }
            }  
        }
        protoAdd(typ, sys, (init == exit) ? exit - rmAct : init, exit, sleep, delta);
    }
    protoAdd(EXIT, sys, init, exit, 0, 0);
    ExitThread(0);
}
Die beiden Komponenten Hauptprogramm und Thread unterhalten sich über volatile int Tim::activationCtrl = -1;

Diese Variable hat drei Zustände:


activationCtrl < 0

(re-)Initialisierungszustand: Thread ist idle. Nach threadParams->cycleMsecs Millisekunden wird nachgeschaut, ob das Hauptprogramm die Variable activationCtrl auf einen Wert >0 gesetzt hat.

activationCtrl == 0

Wenn die theoretische Zeitspanne bis zum nächsten Zyklus abgelaufen ist (realisiert via Steuervariable

if(sleep>2)Sleep(sleep/2);
), wird dieser Wert vom Thread eingestellt.

activationCtrl > 0

Dieser Wert wird vom Hauptprogramm eingestellt, um dem Thread die theoretische Zeitspanne des nächsten Zyklus mitzuteilen.

Auf diese Weise erspart man sich die Einführung eines Semaphores.
Die Funktion protoAdd(...) protokolliert die Zustände des Threads für eine spätere Auswertung durch den Anwender.

Grafische Darstellung:

Der folgende Windows-Dialog stellt einen Versuch dar, einen derartigen aufgezeichneten Zeitablauf dem Anwender in möglichst übersichtlicher Form darzustellen, damit dieser eventuelle Modifikationen und Anpassungen (in seiner Makrosprachen-Source) vornehmen kann.

Zeitablauf Übersicht

Alle gezeigten (Zeit-)Werte von abgelaufenen Zeiten und Zeitspannen sind im Format hours:mins:secs.milliseconds notiert.

Der grüne Balken an der Unterseite des Windows zeigt den theoretischen Zeitablauf definiert durch die exekutierte Sequenz von ZEITKONTROLLPUNKT-Instruktionen. Jedes Rechteck korrespondiert mit einer ZEITKONTROLLPUNKT-Instruktion und trägt zwei Zeitwerte bezüglich der ersten ZEITKONTROLLPUNKT-Instruktion an der linken bzw. rechten Seite des Rechtecks.

Der blaue Balken im unteren Mittel des Windows zeigt den effektiven Zeitablauf. Analog zum grünen Balken repräsentieren die beiden Zeitwerte an den Seiten des Rechtecks die tatsächlich vergangene Zeit beim Erreichen der ZEITKONTROLLPUNKT-Instruktion bzw. beim Erreichen der folgenden ZEITKONTROLLPUNKT-Instruktion. Die schraffierten Bereiche markieren Zeitbereiche, in denen das Hauptprogramm darauf wartete, dass der theoretisch ermittelte Zeitpunkt erreicht werden sollte.

Das rosarote Diagramm im oberen Mittel des Windows zeigt die Abweichung des tatsächlichen Erreichens der entsprechenden ZEITKONTROLLPUNKT-Instruktion relativ zur theoretischen Definition. Die Rasterlinien sind relativ zur Maximalabweichung der gesamten Sequenz zu verstehen.
Die obersten grauen Elemente des Windows zeigen die Hantierungseinheiten und (im Falle, dass sich der Mauszeiger über dem theoretischen grünen Balken befindet,) die im jeweiligen Zyklus ausgeführten Operationen.

Zurück