Forum: PC-Programmierung Timer-Drift-Problem in C++(n Funktions-Aufrufe pro Sekunde)


von Albert (Gast)


Lesenswert?

Hallo,

ich benötige für eine Win32-Konsolenanwendung(Visual C++ 2008 Express) 
eine bestimmte Anzahl von Zeitstempeln pro Sekunde. Die Abstände der 
Zeitstempel sollen durch die Angabe von Millisekunden festgelegt werden.

Meine Versuche, dieses Ziel mit einfachen Timern zu lösen, waren bislang 
erfolglos. Sowohl SetTimer als auch WaitableTimer haben offenbar eine 
gewisse Drift, die über kurz oder lang dazu führt, dass immer wieder mal 
ein Zeitstempel zu wenig erzeugt wird.

Ein Beispiel: Ich stelle das Intervall auf 100ms und sollte daher 10 
Zeitstempel pro Sekunde erhalten. Alle paar Sekunden erhalte ich jedoch 
nur 9 Zeitstempel pro Sekunde, da die Drift dazu führt, dass die 
einzelnen Zeitstempel immer wieder mal um ca. 15ms verzögert erscheinen 
und sich dies auch auf die nachfolgenden Zeitstempel auswirkt, so dass 
der 10. Wert dann irgendwann durch den Überlauf bei 999ms zur 
nachfolgenden Sekunde gehört. Stelle ich das Intervall auf 20ms, erhalte 
ich immerhin noch 49 Messungen pro Sekunde. An mangelnder 
Geschwindigkeit meines Systems bzw. dem Code im Timeraufruf kann es 
somit nicht auscchließlich liegen. Bei z.B. 250ms funktioniert das ganze 
wunderbar 4 mal pro Sekunde exakt ohne Drift, obwohl statistisch doch 
auch da irgendwann eine Verzögerung zum Tragen kommen müsste?!?

Mir ist klar, dass Windows kein Echtzeit-Betriebssystem ist und es geht 
mir auch nicht um auf eine Millisekunde genaue Zeitstempel. Ich suche 
vielmehr nach einer einfachen Lösung, um eine Funktion möglichst 
periodisch aufzurufen. Wenn die Millisekunden der einzelnen Zeitstempel 
dann im niedrigen zweistelligen Bereich schwanken, würde mich das nicht 
stören, so lange in jedem von mir festgelegten Millisekunden-Intervall 
genau ein Funktionsaufruf erfolgt.

Wirken sich Verzögerungen bei einem Timeraufruf immer auch auf die 
nachfolgenden Timeraufrufe aus? Oder ist das Programm fehlerhaft?
1
#include <stdio.h>
2
#include <tchar.h>
3
#include <iostream>
4
#include <Windows.h>
5
#include <conio.h>
6
7
using namespace std;
8
SYSTEMTIME now;
9
10
int _tmain(int argc, _TCHAR* argv[])
11
{
12
    int count = 0;
13
14
    HANDLE hTimer = NULL; 
15
    hTimer = CreateWaitableTimer( 
16
        NULL,
17
        FALSE, 
18
        NULL);
19
20
    SYSTEMTIME time;
21
    GetSystemTime(&time);
22
    time.wSecond += 1; 
23
    FILETIME ftime;
24
    SystemTimeToFileTime(&time, &ftime);
25
26
27
    if(!SetWaitableTimer(
28
        hTimer,
29
        reinterpret_cast<LARGE_INTEGER*>(&ftime),
30
        100,
31
        NULL,
32
        NULL,
33
        0))
34
    {
35
            cout << "SetWaitableTimer failed" <<  GetLastError() << endl;
36
    };
37
38
    
39
    while(WaitForSingleObject(hTimer, 5000) == WAIT_OBJECT_0){
40
  ++count;
41
      GetSystemTime( &now );
42
  printf ("%02d.%02d.%02d.%01d",now.wHour, now.wMinute, now.wSecond, now.wMilliseconds);
43
  printf ("\n");
44
        if(count+1 > 100){
45
            break;
46
        }
47
    }
48
        
49
    CancelWaitableTimer(hTimer);
50
    CloseHandle(hTimer);
51
  system("PAUSE");
52
    return 0;
53
}

Ich könnte jetzt natürlich einen Timer mit möglichst kurzer 
Periodendauer programmieren, der dann unter Berücksichtigung der 
eigentlich gewünschten Periodendauer ständig prüft, ob er in der 
jeweiligen Periode bereits einen Zeitstempel ausgegeben hat - aber ist 
das wirklich notwendig bzw. kann mir ein Timer nicht genau diese Arbeit 
abnehmen?

Gruß,
Albert

von Reinhard R. (reinhardr)


Lesenswert?

Hi,

ich Frage mich ob dein Timer driftet oder ob gelegentlich ein Event 
"verschluckt" wird. Wirf mal einen genauen Blick auf die Zeiten die dir 
dein Programm auswirft. Für einen simplen Test würde ich einfach 
GetTickCount() anstatt GetSystemTime() verwenden und immer die Differenz 
zum vorherigen Schleifendurchlauf ausgeben.

Interessant wird es dann zu sehen, ob die Werte immer >=100ms sind, oder 
ob sie im Zeitmittel bei 100ms mit gelegentlichen Ausreißern von n*100ms 
sind. In einem Histogramm sollte man das relativ gut sehen können, so 
lange der Jitter im Vergleich zur Periodendauer nicht zu groß wird.

Das erste Szenario (immer >= 100ms) würde darauf hindeuten dass der 
Timer zwar 100ms läuft, aber dazu noch diverser Overhead kommt den das 
Betriebssystem nicht berücksichtigt. Das wäre aber imho ein 
Armutszeugnis für einen Timer den man periodisch starten kann.
Ich vermute mal dass du unter Windows schon relativ große Chancen hast 
dass gelegentlich der Prozess nicht zuverlässig alle 100ms zum Zug 
kommt. Du kannst auch versuchen die Priorität deines Prozesses im 
Taskmanager zu erhöhen und möglichst alle ungenützten Programme zu 
beenden.

Gruß,
Reinhard

von Peter II (Gast)


Lesenswert?

du könntest auch die Zeit anbragen und den nächsten Zeitpunkt berechnen, 
also nicht immer stur 100ms warten.

von Reinhard R. (reinhardr)


Angehängte Dateien:

Lesenswert?

Ich habe dein Programm geringfügig modifiziert ausprobiert:
1
  DWORD last_tick = 0, now_tick = 0;
2
  while(WaitForSingleObject(hTimer, 5000) == WAIT_OBJECT_0)
3
  {
4
    now_tick = GetTickCount();
5
    printf("%d\n", now_tick - last_tick);
6
    last_tick = now_tick;
7
8
    ++count;
9
    if(count > 1000){
10
      break;
11
    }
12
  }

Der Timer tut eigentlich dass was er soll. Er driftet nicht davon - bei 
1000 Werten war der Mittelwert +/- 10µs vom Sollwert entfernt. Die 
Standardabweichung bewegte sich in der Größenordnung 7ms. Was ich dabei 
interessant fand, war dass es immer nur eine handvoll diskrete Werte gab 
die immer wieder auftauchten, siehe das angehängte Pseudohistogramm 
(x-Achse nicht skaliert!).
Wenn der Timer mal nicht rechtzeitig zum Zug kommt, was bei mir so ab 
25ms der Fall war, läuft er gleich danach noch einmal durch. Im 
Extremfall, mit eingestellten 5ms, sieht die Ausgabe dann in etwa so 
aus:
0
0
16
0
0
15
0

Um auf deine Situation zurückzukommen: Wenn du in einer Sekunde mal nur 
9, statt 10 Zeitstempeln bekommst bedeutet das, dass du in einer anderen 
dafür 11 bekommst. Wenn du Pech hast, bekommst du eine Weile lang gar 
nichts, aber dann dafür eine Menge identischer Zeitstempel.

Wirf mal einen Blick auf die Multimedia Timer von Windows, vielleicht 
helfen die dir ja weiter:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd743609.aspx


Gruß,
Reinhard

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Die von Reinhard vorgeschlagenen Multimediatimer sollten weiterhelfen.

Einerseits lässt sich mit denen die Timergranularität des Schedulers von 
den üblichen 10 msec auf eine msec verringern (mit timeBeginPeriod), 
andererseits bieten die auch Funktionen an, in einem definierten Takt 
eine Rückruffunktion aufzurufen (mit timeSetEvent) - und das 
funktioniert erheblich stabiler und zuverlässiger als das, was der 
Threadstarter da schildert.

Auch wenn die Dokumentation timeSetEvent als obsolete bezeichnet, man 
kann sie verwenden.


Worauf Du allerdings achten solltest, ist, daß die aufgerufene 
Rückruffunktion nicht allzuviel Rechenzeit verbraucht. Bedenke auch, daß 
sie in einem anderen Threadkontext läuft als Dein Hauptprogramm.

von Reinhard R. (reinhardr)


Lesenswert?

> timeBeginPeriod

Dazu spuckt Google einen interessanten Artikel aus:
http://www.geisswerks.com/ryan/FAQS/timing.html

von Albert (Gast)


Lesenswert?

Hallo,

vielen Dank für eure Hilfe erstmal. Von Reinhard inspiriert habe ich 
noch ein paar Tests gemacht. Neben dem Zeitstempel gebe ich noch die 
aktuelle Timer-Runde sowie die zeitliche Differenz zwischen zwei 
Timer-Aufrufen aus:
1
#include <stdio.h>
2
#include <tchar.h>
3
#include <iostream>
4
#include <Windows.h>
5
#include <conio.h>
6
7
using namespace std;
8
SYSTEMTIME now;
9
  DWORD time1, time2;
10
11
int _tmain(int argc, _TCHAR* argv[])
12
{
13
    int count = 0;
14
15
    HANDLE hTimer = NULL; 
16
    hTimer = CreateWaitableTimer( 
17
        NULL,
18
        FALSE, 
19
        NULL);
20
21
    SYSTEMTIME time;
22
    GetSystemTime(&time);
23
    FILETIME ftime;
24
    SystemTimeToFileTime(&time, &ftime);
25
26
    if(!SetWaitableTimer(
27
        hTimer,
28
        reinterpret_cast<LARGE_INTEGER*>(&ftime),
29
        100,
30
        NULL,
31
        NULL,
32
        0))
33
    {
34
            cout << "SetWaitableTimer failed" <<  GetLastError() << endl;
35
    };
36
37
    time1=GetTickCount();
38
  time2=time1;
39
40
    while(WaitForSingleObject(hTimer, 5000) == WAIT_OBJECT_0){
41
  ++count;
42
43
  
44
  time2=GetTickCount();
45
  GetSystemTime( &now );
46
printf ("%02d.%02d.%02d.%03d - ",now.wHour, now.wMinute, now.wSecond, now.wMilliseconds);
47
printf ("%03d - %d\n", count, time2-time1);
48
time1=time2;
49
if(count+1 > 200){
50
            break;
51
        }
52
    }
53
        
54
    CancelWaitableTimer(hTimer);
55
    CloseHandle(hTimer);
56
  system("PAUSE");
57
    return 0;
58
}

Die folgenden Werte habe ich dann als Ergebnis erhalten:
1
11.38.42.796 - 001 - 0
2
11.38.42.890 - 002 - 94
3
11.38.43.000 - 003 - 109
4
11.38.43.093 - 004 - 94
5
11.38.43.203 - 005 - 109
6
11.38.43.296 - 006 - 94
7
11.38.43.390 - 007 - 94
8
11.38.43.500 - 008 - 109
9
11.38.43.593 - 009 - 94
10
11.38.43.703 - 010 - 109
11
11.38.43.796 - 011 - 94
12
11.38.43.906 - 012 - 109
13
11.38.44.000 - 013 - 94
14
11.38.44.093 - 014 - 94
15
11.38.44.203 - 015 - 109
16
11.38.44.296 - 016 - 94
17
11.38.44.406 - 017 - 109
18
11.38.44.500 - 018 - 94
19
11.38.44.609 - 019 - 109
20
11.38.44.703 - 020 - 94
21
11.38.44.812 - 021 - 110
22
11.38.44.906 - 022 - 93
23
11.38.45.000 - 023 - 94
24
11.38.45.109 - 024 - 109
25
11.38.45.203 - 025 - 94
26
11.38.45.312 - 026 - 110
27
11.38.45.406 - 027 - 93
28
11.38.45.515 - 028 - 110
29
11.38.45.609 - 029 - 93
30
11.38.45.703 - 030 - 94
31
11.38.45.812 - 031 - 110
32
11.38.45.906 - 032 - 93
33
11.38.46.015 - 033 - 110
34
11.38.46.109 - 034 - 93
35
11.38.46.218 - 035 - 110
36
11.38.46.312 - 036 - 94
37
11.38.46.421 - 037 - 109
38
11.38.46.515 - 038 - 94
39
11.38.46.609 - 039 - 93
40
11.38.46.718 - 040 - 110
41
11.38.46.812 - 041 - 94
42
11.38.46.921 - 042 - 109
43
11.38.47.015 - 043 - 94
44
11.38.47.125 - 044 - 109
45
11.38.47.218 - 045 - 94
46
11.38.47.312 - 046 - 94
47
11.38.47.421 - 047 - 109
48
11.38.47.515 - 048 - 94
49
11.38.47.625 - 049 - 109
50
11.38.47.718 - 050 - 94
51
11.38.47.828 - 051 - 109
52
11.38.47.921 - 052 - 94
53
11.38.48.031 - 053 - 109
54
11.38.48.125 - 054 - 94
55
11.38.48.218 - 055 - 94
56
11.38.48.328 - 056 - 109
57
11.38.48.421 - 057 - 94
58
11.38.48.531 - 058 - 109
59
11.38.48.625 - 059 - 94
60
11.38.48.734 - 060 - 109
61
11.38.48.828 - 061 - 94
62
11.38.48.921 - 062 - 94
63
11.38.49.031 - 063 - 109
64
11.38.49.125 - 064 - 94
65
11.38.49.234 - 065 - 109
66
11.38.49.328 - 066 - 94
67
11.38.49.437 - 067 - 110
68
11.38.49.531 - 068 - 93
69
11.38.49.640 - 069 - 110
70
11.38.49.734 - 070 - 93
71
11.38.49.828 - 071 - 94
72
11.38.49.937 - 072 - 110
73
11.38.50.031 - 073 - 93
74
11.38.50.140 - 074 - 110
75
11.38.50.234 - 075 - 93
76
11.38.50.343 - 076 - 110
77
11.38.50.437 - 077 - 94
78
11.38.50.531 - 078 - 93
79
11.38.50.640 - 079 - 110
80
11.38.50.734 - 080 - 93
81
11.38.50.843 - 081 - 110
82
11.38.50.937 - 082 - 94
83
11.38.51.046 - 083 - 109
84
11.38.51.140 - 084 - 94
85
11.38.51.250 - 085 - 109
86
11.38.51.343 - 086 - 94
87
11.38.51.437 - 087 - 94
88
11.38.51.546 - 088 - 109
89
11.38.51.640 - 089 - 94
90
11.38.51.750 - 090 - 109
91
11.38.51.843 - 091 - 94
92
11.38.51.953 - 092 - 109
93
11.38.52.046 - 093 - 94
94
11.38.52.140 - 094 - 94
95
11.38.52.250 - 095 - 109
96
11.38.52.343 - 096 - 94
97
11.38.52.453 - 097 - 109
98
11.38.52.546 - 098 - 94
99
11.38.52.656 - 099 - 109
100
11.38.52.750 - 100 - 94
101
11.38.52.859 - 101 - 109
102
11.38.52.953 - 102 - 94
103
11.38.53.046 - 103 - 94
104
11.38.53.156 - 104 - 109
105
11.38.53.250 - 105 - 94
106
11.38.53.359 - 106 - 109
107
11.38.53.453 - 107 - 94
108
11.38.53.562 - 108 - 110
109
11.38.53.656 - 109 - 93
110
11.38.53.750 - 110 - 94
111
11.38.53.859 - 111 - 109
112
11.38.53.953 - 112 - 94
113
11.38.54.062 - 113 - 110
114
11.38.54.156 - 114 - 93
115
11.38.54.265 - 115 - 110
116
11.38.54.359 - 116 - 93
117
11.38.54.468 - 117 - 110
118
11.38.54.562 - 118 - 94
119
11.38.54.656 - 119 - 93
120
11.38.54.765 - 120 - 110
121
11.38.54.859 - 121 - 93
122
11.38.54.968 - 122 - 110
123
11.38.55.062 - 123 - 94
124
11.38.55.171 - 124 - 109
125
11.38.55.265 - 125 - 94
126
11.38.55.359 - 126 - 93
127
11.38.55.468 - 127 - 110
128
11.38.55.562 - 128 - 94
129
11.38.55.671 - 129 - 109
130
11.38.55.765 - 130 - 94
131
11.38.55.875 - 131 - 109
132
11.38.55.968 - 132 - 94
133
11.38.56.078 - 133 - 109
134
11.38.56.171 - 134 - 94
135
11.38.56.265 - 135 - 94
136
11.38.56.375 - 136 - 109
137
11.38.56.468 - 137 - 94
138
11.38.56.578 - 138 - 109
139
11.38.56.671 - 139 - 94
140
11.38.56.781 - 140 - 109
141
11.38.56.875 - 141 - 94
142
11.38.56.968 - 142 - 94
143
11.38.57.078 - 143 - 109
144
11.38.57.171 - 144 - 94
145
11.38.57.281 - 145 - 109
146
11.38.57.375 - 146 - 94
147
11.38.57.484 - 147 - 109
148
11.38.57.578 - 148 - 94
149
11.38.57.687 - 149 - 110
150
11.38.57.781 - 150 - 93
151
11.38.57.875 - 151 - 94
152
11.38.57.984 - 152 - 109
153
11.38.58.078 - 153 - 94
154
11.38.58.187 - 154 - 110
155
11.38.58.281 - 155 - 93
156
11.38.58.390 - 156 - 110
157
11.38.58.484 - 157 - 93
158
11.38.58.578 - 158 - 94
159
11.38.58.687 - 159 - 110
160
11.38.58.781 - 160 - 93
161
11.38.58.890 - 161 - 110
162
11.38.58.984 - 162 - 93
163
11.38.59.093 - 163 - 110
164
11.38.59.187 - 164 - 94
165
11.38.59.296 - 165 - 109
166
11.38.59.390 - 166 - 94
167
11.38.59.484 - 167 - 93
168
11.38.59.593 - 168 - 110
169
11.38.59.687 - 169 - 94
170
11.38.59.796 - 170 - 109
171
11.38.59.890 - 171 - 94
172
11.39.00.000 - 172 - 109
173
11.39.00.093 - 173 - 94
174
11.39.00.187 - 174 - 94
175
11.39.00.296 - 175 - 109
176
11.39.00.390 - 176 - 94
177
11.39.00.500 - 177 - 109
178
11.39.00.593 - 178 - 94
179
11.39.00.703 - 179 - 109
180
11.39.00.796 - 180 - 94
181
11.39.00.906 - 181 - 109
182
11.39.01.000 - 182 - 94
183
11.39.01.093 - 183 - 94
184
11.39.01.203 - 184 - 109
185
11.39.01.296 - 185 - 94
186
11.39.01.406 - 186 - 109
187
11.39.01.500 - 187 - 94
188
11.39.01.609 - 188 - 109
189
11.39.01.703 - 189 - 94
190
11.39.01.796 - 190 - 94
191
11.39.01.906 - 191 - 109
192
11.39.02.000 - 192 - 94
193
11.39.02.109 - 193 - 109
194
11.39.02.203 - 194 - 94
195
11.39.02.312 - 195 - 110
196
11.39.02.406 - 196 - 93
197
11.39.02.515 - 197 - 110
198
11.39.02.609 - 198 - 93
199
11.39.02.703 - 199 - 94
200
11.39.02.812 - 200 - 110

Wenn ich das richtig sehe, driftet mein Timer definitiv: immer wieder 
mal ungefähr +15ms(z.B. in Sekunde 43 und dann wieder in Sekunde 46, 
usw.). In Sekunde 59 führt diese Drift dann zu einem Überlauf bzw. dazu, 
dass nur 9 Zeitstempel angezeigt werden. Was mich jetzt wundert ist, 
dass es bei Dir, Reinhard, anscheinend zu keiner Timer-Drift kommt. 
Könntest Du vielleicht den von mir verwendeten Code auf Deinem Rechner 
testen?

Ich werde mir heute aber auch mal die von euch vorgeschlagenen 
Multimediatimer ansehen.

Gruß,
Albert

von Peter II (Gast)


Lesenswert?

@ Albert

hast du eventuell ein AMD DualCore system? Dann die Athlons haben ohne 
das Passen AMD-Tool ein Problem mit dem Timer. Jeder Core hat seinen 
eignen Timer und sie laufen nicht Synchron. Wenn jetzt Windows deinen 
Thread zwischen den Cores verteilt dann kann das durchaus zu diesen 
Problemen kommen. Als abhilfe gibt es von amd den Dualcore optimieren 
oder per software kann man die anwendung fest an einen core binden.

von Albert (Gast)


Lesenswert?

Hallo Peter II,

ich habe in der Tat ein AMD Dualcore System(Athlon X2-BE2350). 
Allerdings driftet der Timer auch auf meinem anderen XP-Rechner mit 
Intel T2300 Prozessor.

Viele Grüße,
Albert

von Reinhard R. (reinhardr)


Angehängte Dateien:

Lesenswert?

Hallo Albert,

dein Code funktioniert bei mir so wie er soll ohne Drift, während bei 
dir das offensichtlich der Fall ist. Woher das kommt kann ich nicht 
sagen. Hier läuft ein ein Core2Duo unter W7 x64, kompiliert wurde mit 
VS2010.

Gruß,
Reinhard

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.