Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | [win32] optimized calibration cycle (makes Tcl for windows "RTS" resp. NRT-capable):
- the clock ticks never backwards (avoid it by negative drifts using comparison of times before and after calibration);
- more precise, smooth/soft drifting (avoids too large drifts, already after 10 iterations the drift gets fewer as 0.1 microseconds);
- because of more accurate drifting (aspire to the smallest difference), we can prolong calibration interval (up to 10 seconds by small tdiff-value);
Closes ticket [b7b707a310ea42e9f1b29954ee8ca13ae91ccabe] "[win32] NRT-only - NativeGetTime backwards time-drifts bug" |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | sebres-8-5-timerate |
Files: | files | file ages | folders |
SHA1: |
f6637d3dd85af2a35101e2461c5769eb |
User & Date: | sebres 2017-05-23 21:57:13 |
References
2019-03-05
| ||
20:23 | • Closed ticket [b7b707a310]: win32 NRT-only - NativeGetTime backwards time-drifts bug (fixed) plus 5 other changes artifact: a3ec7b716e user: sebres | |
2019-02-13
| ||
02:57 | merge 8-5-timerate (?max-count?, break possibility, diverse fixes) + windows time-calibration cycle ... check-in: 2f5413a0fb user: sebres tags: sebres-8-6-timerate | |
2017-05-23
| ||
22:01 | • Pending ticket [b7b707a310]: win32 NRT-only - NativeGetTime backwards time-drifts bug (fixed) plus 3 other changes artifact: eabd04466f user: sebres | |
Context
2019-02-12
| ||
18:41 | merge 8.5 (note: to avoid too many conflicts, merged using `fossil merge --baseline 0055a16a8b core-... check-in: 0e846f3e6f user: sergey.brester tags: sebres-8-5-timerate | |
2017-07-03
| ||
13:21 | [performance] after-event list optimized (interp-assoc switched to doubly linked list, because requi... check-in: f562bee7a0 user: sebres tags: sebres-8-5-event-perf-branch | |
2017-05-23
| ||
21:57 | [win32] optimized calibration cycle (makes Tcl for windows "RTS" resp. NRT-capable): - the clock ti... check-in: f6637d3dd8 user: sebres tags: sebres-8-5-timerate | |
2017-05-16
| ||
12:41 | back-ported branch sebres-8-6-timerate (new command "timerate" for 8.5) check-in: c8c40eda06 user: sebres tags: sebres-8-5-timerate | |
Changes
Changes to win/tclWinTime.c.
︙ | ︙ | |||
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | typedef struct TimeInfo { CRITICAL_SECTION cs; /* Mutex guarding this structure. */ int initialized; /* Flag == 1 if this structure is * initialized. */ int perfCounterAvailable; /* Flag == 1 if the hardware has a performance * counter. */ HANDLE calibrationThread; /* Handle to the thread that keeps the virtual * clock calibrated. */ HANDLE readyEvent; /* System event used to trigger the requesting * thread when the clock calibration procedure * is initialized for the first time. */ HANDLE exitEvent; /* Event to signal out of an exit handler to * tell the calibration loop to terminate. */ LARGE_INTEGER nominalFreq; /* Nominal frequency of the system performance * counter, that is, the value returned from * QueryPerformanceFrequency. */ | > < > > | > > | | | | > | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | typedef struct TimeInfo { CRITICAL_SECTION cs; /* Mutex guarding this structure. */ int initialized; /* Flag == 1 if this structure is * initialized. */ int perfCounterAvailable; /* Flag == 1 if the hardware has a performance * counter. */ DWORD calibrationInterv; /* Calibration interval in seconds (start 1 sec) */ HANDLE calibrationThread; /* Handle to the thread that keeps the virtual * clock calibrated. */ HANDLE readyEvent; /* System event used to trigger the requesting * thread when the clock calibration procedure * is initialized for the first time. */ HANDLE exitEvent; /* Event to signal out of an exit handler to * tell the calibration loop to terminate. */ LARGE_INTEGER nominalFreq; /* Nominal frequency of the system performance * counter, that is, the value returned from * QueryPerformanceFrequency. */ /* * The following values are used for calculating virtual time. Virtual * time is always equal to: * lastFileTime + (current perf counter - lastCounter) * * 10000000 / curCounterFreq * and lastFileTime and lastCounter are updated any time that virtual time * is returned to a caller. */ ULARGE_INTEGER fileTimeLastCall; LARGE_INTEGER perfCounterLastCall; LARGE_INTEGER curCounterFreq; LARGE_INTEGER posixEpoch; /* Posix epoch expressed as 100-ns ticks since * the windows epoch. */ /* * Data used in developing the estimate of performance counter frequency */ Tcl_WideUInt fileTimeSample[SAMPLES]; /* Last 64 samples of system time. */ Tcl_WideInt perfCounterSample[SAMPLES]; /* Last 64 samples of performance counter. */ int sampleNo; /* Current sample number. */ } TimeInfo; static TimeInfo timeInfo = { { NULL, 0, 0, NULL, NULL, 0 }, 0, 0, 1, (HANDLE) NULL, (HANDLE) NULL, (HANDLE) NULL, #ifdef HAVE_CAST_TO_UNION (LARGE_INTEGER) (Tcl_WideInt) 0, (ULARGE_INTEGER) (DWORDLONG) 0, (LARGE_INTEGER) (Tcl_WideInt) 0, (LARGE_INTEGER) (Tcl_WideInt) 0, (LARGE_INTEGER) (Tcl_WideInt) 0, #else {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, #endif { 0 }, { 0 }, 0 }; /* |
︙ | ︙ | |||
459 460 461 462 463 464 465 466 467 468 469 | * clock (obtained through ftime) and the frequency of the performance * counter. Also spins a thread whose function is to wake up periodically * and monitor these values, adjusting them as necessary to correct for * drift in the performance counter's oscillator. * *---------------------------------------------------------------------- */ static Tcl_WideInt NativeGetMicroseconds(void) { | > > > > > > > > > > > < < < | | | 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 | * clock (obtained through ftime) and the frequency of the performance * counter. Also spins a thread whose function is to wake up periodically * and monitor these values, adjusting them as necessary to correct for * drift in the performance counter's oscillator. * *---------------------------------------------------------------------- */ static inline Tcl_WideInt NativeCalc100NsTicks( ULONGLONG fileTimeLastCall, LONGLONG perfCounterLastCall, LONGLONG curCounterFreq, LONGLONG curCounter ) { return fileTimeLastCall + ((curCounter - perfCounterLastCall) * 10000000 / curCounterFreq); } static Tcl_WideInt NativeGetMicroseconds(void) { /* * Initialize static storage on the first trip through. * * Note: Outer check for 'initialized' is a performance win since it * avoids an extra mutex lock in the common case. */ if (!timeInfo.initialized) { TclpInitLock(); if (!timeInfo.initialized) { timeInfo.posixEpoch.LowPart = 0xD53E8000; timeInfo.posixEpoch.HighPart = 0x019DB1DE; timeInfo.perfCounterAvailable = QueryPerformanceFrequency(&timeInfo.nominalFreq); /* * Some hardware abstraction layers use the CPU clock in place of * the real-time clock as a performance counter reference. This |
︙ | ︙ | |||
584 585 586 587 588 589 590 | if (timeInfo.perfCounterAvailable && timeInfo.curCounterFreq.QuadPart!=0) { /* * Query the performance counter and use it to calculate the current * time. */ | | | < < < < | | | | | | < | | > | | < < < < | 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 | if (timeInfo.perfCounterAvailable && timeInfo.curCounterFreq.QuadPart!=0) { /* * Query the performance counter and use it to calculate the current * time. */ ULONGLONG fileTimeLastCall; LONGLONG perfCounterLastCall, curCounterFreq; /* Copy with current data of calibration cycle */ LARGE_INTEGER curCounter; /* Current performance counter. */ QueryPerformanceCounter(&curCounter); /* * Hold time section locked as short as possible */ EnterCriticalSection(&timeInfo.cs); fileTimeLastCall = timeInfo.fileTimeLastCall.QuadPart; perfCounterLastCall = timeInfo.perfCounterLastCall.QuadPart; curCounterFreq = timeInfo.curCounterFreq.QuadPart; LeaveCriticalSection(&timeInfo.cs); /* * If calibration cycle occurred after we get curCounter */ if (curCounter.QuadPart <= perfCounterLastCall) { /* Calibrated file-time is saved from posix in 100-ns ticks */ return fileTimeLastCall / 10; } /* * If it appears to be more than 1.1 seconds since the last trip * through the calibration loop, the performance counter may have * jumped forward. (See MSDN Knowledge Base article Q274323 for a * description of the hardware problem that makes this test * necessary.) If the counter jumps, we don't want to use it directly. * Instead, we must return system time. Eventually, the calibration * loop should recover. */ if (curCounter.QuadPart - perfCounterLastCall < 11 * curCounterFreq * timeInfo.calibrationInterv / 10 ) { /* Calibrated file-time is saved from posix in 100-ns ticks */ return NativeCalc100NsTicks(fileTimeLastCall, perfCounterLastCall, curCounterFreq, curCounter.QuadPart) / 10; } } /* * High resolution timer is not available. */ return 0; |
︙ | ︙ | |||
705 706 707 708 709 710 711 712 713 714 715 716 717 718 | * * Side effects: * Sets the 'exitEvent' event in the 'timeInfo' structure to ask the * thread in question to exit, and waits for it to do so. * *---------------------------------------------------------------------- */ static void StopCalibration( ClientData unused) /* Client data is unused */ { SetEvent(timeInfo.exitEvent); | > > | 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 | * * Side effects: * Sets the 'exitEvent' event in the 'timeInfo' structure to ask the * thread in question to exit, and waits for it to do so. * *---------------------------------------------------------------------- */ void TclWinResetTimerResolution(void); static void StopCalibration( ClientData unused) /* Client data is unused */ { SetEvent(timeInfo.exitEvent); |
︙ | ︙ | |||
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 | */ GetSystemTimeAsFileTime(&curFileTime); QueryPerformanceCounter(&timeInfo.perfCounterLastCall); QueryPerformanceFrequency(&timeInfo.curCounterFreq); timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime; timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime; ResetCounterSamples(timeInfo.fileTimeLastCall.QuadPart, timeInfo.perfCounterLastCall.QuadPart, timeInfo.curCounterFreq.QuadPart); /* * Wake up the calling thread. When it wakes up, it will release the | > > | 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 | */ GetSystemTimeAsFileTime(&curFileTime); QueryPerformanceCounter(&timeInfo.perfCounterLastCall); QueryPerformanceFrequency(&timeInfo.curCounterFreq); timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime; timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime; /* Calibrated file-time will be saved from posix in 100-ns ticks */ timeInfo.fileTimeLastCall.QuadPart -= timeInfo.posixEpoch.QuadPart; ResetCounterSamples(timeInfo.fileTimeLastCall.QuadPart, timeInfo.perfCounterLastCall.QuadPart, timeInfo.curCounterFreq.QuadPart); /* * Wake up the calling thread. When it wakes up, it will release the |
︙ | ︙ | |||
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 | static void UpdateTimeEachSecond(void) { LARGE_INTEGER curPerfCounter; /* Current value returned from * QueryPerformanceCounter. */ FILETIME curSysTime; /* Current system time. */ LARGE_INTEGER curFileTime; /* File time at the time this callback was * scheduled. */ Tcl_WideInt estFreq; /* Estimated perf counter frequency. */ Tcl_WideInt vt0; /* Tcl time right now. */ Tcl_WideInt vt1; /* Tcl time one second from now. */ Tcl_WideInt tdiff; /* Difference between system clock and Tcl * time. */ Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into * step over 1 second. */ /* | > | < > > > > > > > > | > | > < | < | | < < | > | < > > | > > > > > > > > > > > > > | > > | > > > > | > > > > > > > | > > > > > > > > > > > > > > > | | > | > > | > > > > > > > > | 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 | static void UpdateTimeEachSecond(void) { LARGE_INTEGER curPerfCounter; /* Current value returned from * QueryPerformanceCounter. */ FILETIME curSysTime; /* Current system time. */ static LARGE_INTEGER lastFileTime; /* File time of the previous calibration */ LARGE_INTEGER curFileTime; /* File time at the time this callback was * scheduled. */ Tcl_WideInt estFreq; /* Estimated perf counter frequency. */ Tcl_WideInt vt0; /* Tcl time right now. */ Tcl_WideInt vt1; /* Tcl time one second from now. */ Tcl_WideInt tdiff; /* Difference between system clock and Tcl * time. */ Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into * step over 1 second. */ /* * Sample performance counter and system time (from posix epoch). */ GetSystemTimeAsFileTime(&curSysTime); curFileTime.LowPart = curSysTime.dwLowDateTime; curFileTime.HighPart = curSysTime.dwHighDateTime; curFileTime.QuadPart -= timeInfo.posixEpoch.QuadPart; /* If calibration still not needed (check for possible time switch) */ if ( curFileTime.QuadPart > lastFileTime.QuadPart && curFileTime.QuadPart < lastFileTime.QuadPart + (timeInfo.calibrationInterv * 10000000) ) { /* again in next one second */ return; } QueryPerformanceCounter(&curPerfCounter); lastFileTime.QuadPart = curFileTime.QuadPart; /* * We devide by timeInfo.curCounterFreq.QuadPart in several places. That * value should always be positive on a correctly functioning system. But * it is good to be defensive about such matters. So if something goes * wrong and the value does goes to zero, we clear the * timeInfo.perfCounterAvailable in order to cause the calibration thread * to shut itself down, then return without additional processing. */ if (timeInfo.curCounterFreq.QuadPart == 0){ timeInfo.perfCounterAvailable = 0; return; } /* * Several things may have gone wrong here that have to be checked for. * (1) The performance counter may have jumped. * (2) The system clock may have been reset. * * In either case, we'll need to reinitialize the circular buffer with * samples relative to the current system time and the NOMINAL performance * frequency (not the actual, because the actual has probably run slow in * the first case). Our estimated frequency will be the nominal frequency. * * Store the current sample into the circular buffer of samples, and * estimate the performance counter frequency. */ estFreq = AccumulateSample(curPerfCounter.QuadPart, (Tcl_WideUInt) curFileTime.QuadPart); /* * We want to adjust things so that time appears to be continuous. * Virtual file time, right now, is * * vt0 = 10000000 * (curPerfCounter - perfCounterLastCall) * / curCounterFreq * + fileTimeLastCall * * Ideally, we would like to drift the clock into place over a period of 2 * sec, so that virtual time 2 sec from now will be * * vt1 = 20000000 + curFileTime * * The frequency that we need to use to drift the counter back into place * is estFreq * 20000000 / (vt1 - vt0) */ vt0 = NativeCalc100NsTicks(timeInfo.fileTimeLastCall.QuadPart, timeInfo.perfCounterLastCall.QuadPart, timeInfo.curCounterFreq.QuadPart, curPerfCounter.QuadPart); /* * If we've gotten more than a second away from system time, then drifting * the clock is going to be pretty hopeless. Just let it jump. Otherwise, * compute the drift frequency and fill in everything. */ tdiff = vt0 - curFileTime.QuadPart; if (tdiff > 10000000 || tdiff < -10000000) { /* jump to current system time, use curent estimated frequency */ vt0 = curFileTime.QuadPart; } else { /* calculate new frequency and estimate drift to the next second */ vt1 = 20000000 + curFileTime.QuadPart; driftFreq = (estFreq * 20000000 / (vt1 - vt0)); /* * Avoid too large drifts (only half of the current difference), * that allows also be more accurate (aspire to the smallest tdiff), * so then we can prolong calibration interval by tdiff < 100000 */ driftFreq = timeInfo.curCounterFreq.QuadPart + (driftFreq - timeInfo.curCounterFreq.QuadPart) / 2; /* * Average between estimated, 2 current and 5 drifted frequencies, * (do the soft drifting as possible) */ estFreq = (estFreq + 2 * timeInfo.curCounterFreq.QuadPart + 5 * driftFreq) / 8; } /* Avoid too large discrepancy from nominal frequency */ if (estFreq > 1003*timeInfo.nominalFreq.QuadPart/1000) { estFreq = 1003*timeInfo.nominalFreq.QuadPart/1000; vt0 = curFileTime.QuadPart; } else if (estFreq < 997*timeInfo.nominalFreq.QuadPart/1000) { estFreq = 997*timeInfo.nominalFreq.QuadPart/1000; vt0 = curFileTime.QuadPart; } else if (vt0 != curFileTime.QuadPart) { /* * Be sure the clock ticks never backwards (avoid it by negative drifting) * just compare native time (in 100-ns) before and hereafter using * new calibrated values) and do a small adjustment (short time freeze) */ LARGE_INTEGER newPerfCounter; Tcl_WideInt nt0, nt1; QueryPerformanceCounter(&newPerfCounter); nt0 = NativeCalc100NsTicks(timeInfo.fileTimeLastCall.QuadPart, timeInfo.perfCounterLastCall.QuadPart, timeInfo.curCounterFreq.QuadPart, newPerfCounter.QuadPart); nt1 = NativeCalc100NsTicks(vt0, curPerfCounter.QuadPart, estFreq, newPerfCounter.QuadPart); if (nt0 > nt1) { /* drifted backwards, try to compensate with new base */ /* first adjust with a micro jump (short frozen time is acceptable) */ vt0 += nt0 - nt1; /* if drift unavoidable (e. g. we had a time switch), then reset it */ vt1 = vt0 - curFileTime.QuadPart; if (vt1 > 10000000 || vt1 < -10000000) { /* larger jump resp. shift relative new file-time */ vt0 = curFileTime.QuadPart; } } } /* In lock commit new values to timeInfo (hold lock as short as possible) */ EnterCriticalSection(&timeInfo.cs); /* grow calibration interval up to 10 seconds (if still precise enough) */ if (tdiff < -100000 || tdiff > 100000) { /* too long drift - reset calibration interval to 1000 second */ timeInfo.calibrationInterv = 1; } else if (timeInfo.calibrationInterv < 10) { timeInfo.calibrationInterv++; } timeInfo.fileTimeLastCall.QuadPart = vt0; timeInfo.curCounterFreq.QuadPart = estFreq; timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart; LeaveCriticalSection(&timeInfo.cs); } /* *---------------------------------------------------------------------- |
︙ | ︙ |