summaryrefslogtreecommitdiffstats
path: root/win/tclWinTime.c
diff options
context:
space:
mode:
authorKevin B Kenny <kennykb@acm.org>2003-04-12 20:11:33 (GMT)
committerKevin B Kenny <kennykb@acm.org>2003-04-12 20:11:33 (GMT)
commitb0cc421ee0a6c1691d7db034c52ae9fcb5295627 (patch)
treedfa1a81b176b70021b0ecfa66e96c7a6a66d2ba1 /win/tclWinTime.c
parent47554fcd67f382566a72813c4b11bda27ecfb201 (diff)
downloadtcl-b0cc421ee0a6c1691d7db034c52ae9fcb5295627.zip
tcl-b0cc421ee0a6c1691d7db034c52ae9fcb5295627.tar.gz
tcl-b0cc421ee0a6c1691d7db034c52ae9fcb5295627.tar.bz2
Fixed Bug 710310 (duplicate test numbers in clock.test). Made major
changes to tclWinTime.c and related code to improve loop filter stability.
Diffstat (limited to 'win/tclWinTime.c')
-rw-r--r--win/tclWinTime.c361
1 files changed, 240 insertions, 121 deletions
diff --git a/win/tclWinTime.c b/win/tclWinTime.c
index 63da982..ee3995b 100644
--- a/win/tclWinTime.c
+++ b/win/tclWinTime.c
@@ -9,7 +9,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclWinTime.c,v 1.14 2003/02/14 22:16:27 kennykb Exp $
+ * RCS: @(#) $Id: tclWinTime.c,v 1.14.2.1 2003/04/12 20:11:34 kennykb Exp $
*/
#include "tclWinInt.h"
@@ -19,6 +19,11 @@
#define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY)
/*
+ * Number of samples over which to estimate the performance counter
+ */
+#define SAMPLES 64
+
+/*
* The following arrays contain the day of year for the last day of
* each month, where index 1 is January.
*/
@@ -38,13 +43,6 @@ typedef struct ThreadSpecificData {
static Tcl_ThreadDataKey dataKey;
/*
- * Calibration interval for the high-resolution timer, in msec
- */
-
-static CONST unsigned long clockCalibrateWakeupInterval = 10000;
- /* FIXME: 10 s -- should be about 10 min! */
-
-/*
* Data for managing high-resolution timers.
*/
@@ -65,10 +63,14 @@ typedef struct TimeInfo {
* 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.
@@ -79,22 +81,20 @@ typedef struct TimeInfo {
* virtual time is returned to a caller.
*/
- ULARGE_INTEGER lastFileTime;
- LARGE_INTEGER lastCounter;
+ ULARGE_INTEGER fileTimeLastCall;
+ LARGE_INTEGER perfCounterLastCall;
LARGE_INTEGER curCounterFreq;
- /*
- * The next two values are used only in the calibration thread, to track
- * the frequency of the performance counter.
+ /*
+ * Data used in developing the estimate of performance counter
+ * frequency
*/
+ ULONGLONG fileTimeSample[SAMPLES];
+ /* Last 64 samples of system time */
+ LONGLONG perfCounterSample[SAMPLES];
+ /* Last 64 samples of performance counter */
+ int sampleNo; /* Current sample number */
- LONGLONG lastPerfCounter; /* Performance counter the last time
- * that UpdateClockEachSecond was called */
- LONGLONG lastSysTime; /* System clock at the last time
- * that UpdateClockEachSecond was called */
- LONGLONG estPerfCounterFreq;
- /* Current estimate of the counter frequency
- * using the system clock as the standard */
} TimeInfo;
@@ -116,6 +116,10 @@ static TimeInfo timeInfo = {
#endif
0,
0,
+ 0,
+ { 0 },
+ { 0 },
+ 0,
0
};
@@ -129,6 +133,15 @@ static struct tm * ComputeGMT _ANSI_ARGS_((const time_t *tp));
static void StopCalibration _ANSI_ARGS_(( ClientData ));
static DWORD WINAPI CalibrationThread _ANSI_ARGS_(( LPVOID arg ));
static void UpdateTimeEachSecond _ANSI_ARGS_(( void ));
+static void ResetCounterSamples _ANSI_ARGS_((
+ ULONGLONG fileTime,
+ LONGLONG perfCounter,
+ LONGLONG perfFreq
+ ));
+static LONGLONG AccumulateSample _ANSI_ARGS_((
+ LONGLONG perfCounter,
+ ULONGLONG fileTime
+ ));
/*
*----------------------------------------------------------------------
@@ -267,7 +280,7 @@ Tcl_GetTime(timePtr)
TclpInitLock();
if ( !timeInfo.initialized ) {
timeInfo.perfCounterAvailable
- = QueryPerformanceFrequency( &timeInfo.curCounterFreq );
+ = QueryPerformanceFrequency( &timeInfo.nominalFreq );
/*
* Some hardware abstraction layers use the CPU clock
@@ -296,10 +309,10 @@ Tcl_GetTime(timePtr)
if ( timeInfo.perfCounterAvailable
/* The following lines would do an exact match on
* crystal frequency:
- * && timeInfo.curCounterFreq.QuadPart != (LONGLONG) 1193182
- * && timeInfo.curCounterFreq.QuadPart != (LONGLONG) 3579545
+ * && timeInfo.nominalFreq.QuadPart != (LONGLONG) 1193182
+ * && timeInfo.nominalFreq.QuadPart != (LONGLONG) 3579545
*/
- && timeInfo.curCounterFreq.QuadPart > (LONGLONG) 15000000 ) {
+ && timeInfo.nominalFreq.QuadPart > (LONGLONG) 15000000 ) {
timeInfo.perfCounterAvailable = FALSE;
}
@@ -364,26 +377,29 @@ Tcl_GetTime(timePtr)
EnterCriticalSection( &timeInfo.cs );
QueryPerformanceCounter( &curCounter );
+
/*
* If it appears to be more than 1.1 seconds since the last trip
* through the calibration loop, the performance counter may
- * have jumped. Discard it. See MSDN Knowledge Base article
+ * have jumped forward. (See MSDN Knowledge Base article
* Q274323 for a description of the hardware problem that makes
- * this test necessary.
+ * 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 - timeInfo.lastPerfCounter
- < 11 * timeInfo.estPerfCounterFreq / 10 ) {
+ if ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart
+ < 11 * timeInfo.curCounterFreq.QuadPart / 10 ) {
- curFileTime = timeInfo.lastFileTime.QuadPart
- + ( ( curCounter.QuadPart - timeInfo.lastCounter.QuadPart )
+ curFileTime = timeInfo.fileTimeLastCall.QuadPart
+ + ( ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart )
* 10000000 / timeInfo.curCounterFreq.QuadPart );
- timeInfo.lastFileTime.QuadPart = curFileTime;
- timeInfo.lastCounter.QuadPart = curCounter.QuadPart;
+ timeInfo.fileTimeLastCall.QuadPart = curFileTime;
+ timeInfo.perfCounterLastCall.QuadPart = curCounter.QuadPart;
usecSincePosixEpoch = ( curFileTime - posixEpoch.QuadPart ) / 10;
timePtr->sec = (time_t) ( usecSincePosixEpoch / 1000000 );
timePtr->usec = (unsigned long ) ( usecSincePosixEpoch % 1000000 );
useFtime = 0;
- }
+ }
LeaveCriticalSection( &timeInfo.cs );
}
@@ -734,8 +750,7 @@ ComputeGMT(tp)
* None. This thread embeds an infinite loop.
*
* Side effects:
- * At an interval of clockCalibrateWakeupInterval ms, this thread
- * performs virtual time discipline.
+ * At an interval of 1 s, this thread performs virtual time discipline.
*
* Note: When this thread is entered, TclpInitLock has been called
* to safeguard the static storage. There is therefore no synchronization
@@ -753,15 +768,14 @@ CalibrationThread( LPVOID arg )
/* Get initial system time and performance counter */
GetSystemTimeAsFileTime( &curFileTime );
- QueryPerformanceCounter( &timeInfo.lastCounter );
+ QueryPerformanceCounter( &timeInfo.perfCounterLastCall );
QueryPerformanceFrequency( &timeInfo.curCounterFreq );
- timeInfo.lastFileTime.LowPart = curFileTime.dwLowDateTime;
- timeInfo.lastFileTime.HighPart = curFileTime.dwHighDateTime;
-
- /* Initialize the working storage for the calibration callback */
+ timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime;
+ timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime;
- timeInfo.lastPerfCounter = timeInfo.lastCounter.QuadPart;
- timeInfo.estPerfCounterFreq = timeInfo.curCounterFreq.QuadPart;
+ ResetCounterSamples( timeInfo.fileTimeLastCall.QuadPart,
+ timeInfo.perfCounterLastCall.QuadPart,
+ timeInfo.curCounterFreq.QuadPart );
/*
* Wake up the calling thread. When it wakes up, it will release the
@@ -815,34 +829,24 @@ UpdateTimeEachSecond()
/* Current value returned from
* QueryPerformanceCounter */
- LONGLONG perfCounterDiff; /* Difference between the current value
- * and the value of 1 second ago */
-
FILETIME curSysTime; /* Current system time */
LARGE_INTEGER curFileTime; /* File time at the time this callback
* was scheduled. */
- LONGLONG fileTimeDiff; /* Elapsed time on the system clock
- * since the last time this procedure
- * was called */
-
- LONGLONG instantFreq; /* Instantaneous estimate of the
- * performance counter frequency */
-
- LONGLONG delta; /* Increment to add to the estimated
- * performance counter frequency in the
- * loop filter */
+ LONGLONG estFreq; /* Estimated perf counter frequency */
- LONGLONG fuzz; /* Tolerance for the perf counter frequency */
+ LONGLONG vt0; /* Tcl time right now */
+ LONGLONG vt1; /* Tcl time one second from now */
- LONGLONG lowBound; /* Lower bound for the frequency assuming
- * 1000 ppm tolerance */
+ LONGLONG tdiff; /* Difference between system clock and
+ * Tcl time. */
- LONGLONG hiBound; /* Upper bound for the frequency */
+ LONGLONG driftFreq; /* Frequency needed to drift virtual time
+ * into step over 1 second */
/*
- * Get current performance counter and system time.
+ * Sample performance counter and system time.
*/
QueryPerformanceCounter( &curPerfCounter );
@@ -853,86 +857,201 @@ UpdateTimeEachSecond()
EnterCriticalSection( &timeInfo.cs );
/*
- * Find out how many ticks of the performance counter and the
- * system clock have elapsed since we got into this procedure.
- * Estimate the current frequency.
+ * 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.
*/
- perfCounterDiff = curPerfCounter.QuadPart - timeInfo.lastPerfCounter;
- timeInfo.lastPerfCounter = curPerfCounter.QuadPart;
- fileTimeDiff = curFileTime.QuadPart - timeInfo.lastSysTime;
- timeInfo.lastSysTime = curFileTime.QuadPart;
- instantFreq = ( 10000000 * perfCounterDiff / fileTimeDiff );
-
/*
- * Consider this a timing glitch if instant frequency varies
- * significantly from the current estimate.
+ * Store the current sample into the circular buffer of samples,
+ * and estimate the performance counter frequency.
*/
- fuzz = timeInfo.estPerfCounterFreq >> 10;
- lowBound = timeInfo.estPerfCounterFreq - fuzz;
- hiBound = timeInfo.estPerfCounterFreq + fuzz;
- if ( instantFreq < lowBound || instantFreq > hiBound ) {
- LeaveCriticalSection( &timeInfo.cs );
- return;
- }
+ estFreq = AccumulateSample( curPerfCounter.QuadPart,
+ curFileTime.QuadPart );
/*
- * Update the current estimate of performance counter frequency.
- * This code is equivalent to the loop filter of a phase locked
- * loop.
+ * 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 )
*/
-
- delta = ( instantFreq - timeInfo.estPerfCounterFreq ) >> 6;
- timeInfo.estPerfCounterFreq += delta;
+
+ vt0 = 10000000 * ( curPerfCounter.QuadPart
+ - timeInfo.perfCounterLastCall.QuadPart )
+ / timeInfo.curCounterFreq.QuadPart
+ + timeInfo.fileTimeLastCall.QuadPart;
+ vt1 = 20000000 + curFileTime.QuadPart;
/*
- * Update the current virtual time.
+ * 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.
*/
- timeInfo.lastFileTime.QuadPart
- += ( ( curPerfCounter.QuadPart - timeInfo.lastCounter.QuadPart )
- * 10000000 / timeInfo.curCounterFreq.QuadPart );
- timeInfo.lastCounter.QuadPart = curPerfCounter.QuadPart;
+ tdiff = vt0 - curFileTime.QuadPart;
+ if ( tdiff > 10000000 || tdiff < -10000000 ) {
+ timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart;
+ timeInfo.curCounterFreq.QuadPart = estFreq;
+ } else {
+ driftFreq = estFreq * 20000000 / ( vt1 - vt0 );
+ if ( driftFreq > 1003 * estFreq / 1000 ) {
+ driftFreq = 1003 * estFreq / 1000;
+ }
+ if ( driftFreq < 997 * estFreq / 1000 ) {
+ driftFreq = 997 * estFreq / 1000;
+ }
+ timeInfo.fileTimeLastCall.QuadPart = vt0;
+ timeInfo.curCounterFreq.QuadPart = driftFreq;
+ }
- delta = curFileTime.QuadPart - timeInfo.lastFileTime.QuadPart;
- if ( delta > 10000000 || delta < -10000000 ) {
+ timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart;
- /*
- * If the virtual time slip exceeds one second, then adjusting
- * the counter frequency is hopeless (it'll take over fifteen
- * minutes to line up with the system clock). The most likely
- * cause of this large a slip is a sudden change to the system
- * clock, perhaps because it was being corrected by wristwatch
- * and eyeball. Accept the system time, and set the performance
- * counter frequency to the current estimate.
- */
+ LeaveCriticalSection( &timeInfo.cs );
+
+}
- timeInfo.lastFileTime.QuadPart = curFileTime.QuadPart;
- timeInfo.curCounterFreq.QuadPart = timeInfo.estPerfCounterFreq;
+/*
+ *----------------------------------------------------------------------
+ *
+ * ResetCounterSamples --
+ *
+ * Fills the sample arrays in 'timeInfo' with dummy values that will
+ * yield the current performance counter and frequency.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The array of samples is filled in so that it appears that there
+ * are SAMPLES samples at one-second intervals, separated by precisely
+ * the given frequency.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+ResetCounterSamples( ULONGLONG fileTime,
+ /* Current file time */
+ LONGLONG perfCounter,
+ /* Current performance counter */
+ LONGLONG perfFreq )
+ /* Target performance frequency */
+{
+ int i;
+ for ( i = SAMPLES-1; i >= 0; --i ) {
+ timeInfo.perfCounterSample[i] = perfCounter;
+ timeInfo.fileTimeSample[i] = fileTime;
+ perfCounter -= perfFreq;
+ fileTime -= 10000000;
+ }
+ timeInfo.sampleNo = 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * AccumulateSample --
+ *
+ * Updates the circular buffer of performance counter and system
+ * time samples with a new data point.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The new data point replaces the oldest point in the circular
+ * buffer, and the descriptive statistics are updated to accumulate
+ * the new point.
+ *
+ * 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).
+ */
+
+static LONGLONG
+AccumulateSample( LONGLONG perfCounter,
+ ULONGLONG fileTime )
+{
+ ULONGLONG workFTSample; /* File time sample being removed
+ * from or added to the circular buffer */
+
+ LONGLONG workPCSample; /* Performance counter sample being
+ * removed from or added to the circular
+ * buffer */
+
+ ULONGLONG lastFTSample; /* Last file time sample recorded */
+
+ LONGLONG lastPCSample; /* Last performance counter sample recorded */
+
+ LONGLONG FTdiff; /* Difference between last FT and current */
+
+ LONGLONG PCdiff; /* Difference between last PC and current */
+
+ LONGLONG estFreq; /* Estimated performance counter frequency */
+
+ /* Test for jumps and reset the samples if we have one. */
+
+ if ( timeInfo.sampleNo == 0 ) {
+ lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo
+ + SAMPLES - 1 ];
+ lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo
+ + SAMPLES - 1 ];
} else {
+ lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo - 1 ];
+ lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo - 1 ];
+ }
+ PCdiff = perfCounter - lastPCSample;
+ FTdiff = fileTime - lastFTSample;
+ if ( PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10
+ || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10
+ || FTdiff < 9000000
+ || FTdiff > 11000000 ) {
+ ResetCounterSamples( fileTime, perfCounter,
+ timeInfo.nominalFreq.QuadPart );
+ return timeInfo.nominalFreq.QuadPart;
- /*
- * Compute a counter frequency that will cause virtual time to line
- * up with system time one second from now, assuming that the
- * performance counter continues to tick at timeInfo.estPerfCounterFreq.
- */
+ } else {
+
+ /* Estimate the frequency */
- timeInfo.curCounterFreq.QuadPart
- = 10000000 * timeInfo.estPerfCounterFreq / ( delta + 10000000 );
-
- /*
- * Limit frequency excursions to 1000 ppm from estimate
- */
+ workPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo ];
+ workFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo ];
+ estFreq = 10000000 * ( perfCounter - workPCSample )
+ / ( fileTime - workFTSample );
+ timeInfo.perfCounterSample[ timeInfo.sampleNo ] = perfCounter;
+ timeInfo.fileTimeSample[ timeInfo.sampleNo ] = (LONGLONG) fileTime;
- if ( timeInfo.curCounterFreq.QuadPart < lowBound ) {
- timeInfo.curCounterFreq.QuadPart = lowBound;
- } else if ( timeInfo.curCounterFreq.QuadPart > hiBound ) {
- timeInfo.curCounterFreq.QuadPart = hiBound;
- }
+ /* Advance the sample number */
+
+ if ( ++timeInfo.sampleNo >= SAMPLES ) {
+ timeInfo.sampleNo = 0;
+ }
+
+ return estFreq;
}
-
- LeaveCriticalSection( &timeInfo.cs );
-
}