summaryrefslogtreecommitdiffstats
path: root/win/tclWinTime.c
diff options
context:
space:
mode:
Diffstat (limited to 'win/tclWinTime.c')
-rw-r--r--win/tclWinTime.c426
1 files changed, 344 insertions, 82 deletions
diff --git a/win/tclWinTime.c b/win/tclWinTime.c
index dd603d1..bfebbe6 100644
--- a/win/tclWinTime.c
+++ b/win/tclWinTime.c
@@ -53,6 +53,7 @@ typedef struct {
* 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
@@ -63,7 +64,6 @@ typedef struct {
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:
@@ -76,6 +76,8 @@ typedef struct {
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
@@ -92,6 +94,7 @@ static TimeInfo timeInfo = {
{ NULL, 0, 0, NULL, NULL, 0 },
0,
0,
+ 1,
(HANDLE) NULL,
(HANDLE) NULL,
(HANDLE) NULL,
@@ -100,11 +103,13 @@ static TimeInfo timeInfo = {
(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},
+ {0, 0},
+ {0, 0},
#endif
{ 0 },
{ 0 },
@@ -112,6 +117,17 @@ static TimeInfo timeInfo = {
};
/*
+ * Scale to convert wide click values from the TclpGetWideClicks native
+ * resolution to microsecond resolution and back.
+ */
+static struct {
+ int initialized; /* 1 if initialized, 0 otherwise */
+ int perfCounter; /* 1 if performance counter usable for wide clicks */
+ double microsecsScale; /* Denominator scale between clock / microsecs */
+} wideClick = {0, 0.0};
+
+
+/*
* Declarations for functions defined later in this file.
*/
@@ -127,6 +143,7 @@ static Tcl_WideInt AccumulateSample(Tcl_WideInt perfCounter,
Tcl_WideUInt fileTime);
static void NativeScaleTime(Tcl_Time* timebuf,
ClientData clientData);
+static Tcl_WideInt NativeGetMicroseconds(void);
static void NativeGetTime(Tcl_Time* timebuf,
ClientData clientData);
@@ -158,10 +175,19 @@ ClientData tclTimeClientData = NULL;
unsigned long
TclpGetSeconds(void)
{
- Tcl_Time t;
+ Tcl_WideInt usecSincePosixEpoch;
+
+ /* Try to use high resolution timer */
+ if ( tclGetTimeProcPtr == NativeGetTime
+ && (usecSincePosixEpoch = NativeGetMicroseconds())
+ ) {
+ return usecSincePosixEpoch / 1000000;
+ } else {
+ Tcl_Time t;
- tclGetTimeProcPtr(&t, tclTimeClientData); /* Tcl_GetTime inlined. */
- return t.sec;
+ tclGetTimeProcPtr(&t, tclTimeClientData); /* Tcl_GetTime inlined. */
+ return t.sec;
+ }
}
/*
@@ -186,19 +212,147 @@ TclpGetSeconds(void)
unsigned long
TclpGetClicks(void)
{
- /*
- * Use the Tcl_GetTime abstraction to get the time in microseconds, as
- * nearly as we can, and return it.
- */
+ Tcl_WideInt usecSincePosixEpoch;
+
+ /* Try to use high resolution timer */
+ if ( tclGetTimeProcPtr == NativeGetTime
+ && (usecSincePosixEpoch = NativeGetMicroseconds())
+ ) {
+ return (unsigned long)usecSincePosixEpoch;
+ } else {
+ /*
+ * Use the Tcl_GetTime abstraction to get the time in microseconds, as
+ * nearly as we can, and return it.
+ */
- Tcl_Time now; /* Current Tcl time */
- unsigned long retval; /* Value to return */
+ Tcl_Time now; /* Current Tcl time */
- tclGetTimeProcPtr(&now, tclTimeClientData); /* Tcl_GetTime inlined */
+ tclGetTimeProcPtr(&now, tclTimeClientData); /* Tcl_GetTime inlined */
+ return (unsigned long)(now.sec * 1000000) + now.usec;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclpGetWideClicks --
+ *
+ * This procedure returns a WideInt value that represents the highest
+ * resolution clock in microseconds available on the system.
+ *
+ * Results:
+ * Number of microseconds (from some start time).
+ *
+ * Side effects:
+ * This should be used for time-delta resp. for measurement purposes
+ * only, because on some platforms can return microseconds from some
+ * start time (not from the epoch).
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_WideInt
+TclpGetWideClicks(void)
+{
+ LARGE_INTEGER curCounter;
+
+ if (!wideClick.initialized) {
+ LARGE_INTEGER perfCounterFreq;
+
+ /*
+ * The frequency of the performance counter is fixed at system boot and
+ * is consistent across all processors. Therefore, the frequency need
+ * only be queried upon application initialization.
+ */
+ if (QueryPerformanceFrequency(&perfCounterFreq)) {
+ wideClick.perfCounter = 1;
+ wideClick.microsecsScale = 1000000.0 / perfCounterFreq.QuadPart;
+ } else {
+ /* fallback using microseconds */
+ wideClick.perfCounter = 0;
+ wideClick.microsecsScale = 1;
+ }
- retval = (now.sec * 1000000) + now.usec;
- return retval;
+ wideClick.initialized = 1;
+ }
+ if (wideClick.perfCounter) {
+ if (QueryPerformanceCounter(&curCounter)) {
+ return (Tcl_WideInt)curCounter.QuadPart;
+ }
+ /* fallback using microseconds */
+ wideClick.perfCounter = 0;
+ wideClick.microsecsScale = 1;
+ return TclpGetMicroseconds();
+ } else {
+ return TclpGetMicroseconds();
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclpWideClickInMicrosec --
+ *
+ * This procedure return scale to convert wide click values from the
+ * TclpGetWideClicks native resolution to microsecond resolution
+ * and back.
+ *
+ * Results:
+ * 1 click in microseconds as double.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+double
+TclpWideClickInMicrosec(void)
+{
+ if (!wideClick.initialized) {
+ (void)TclpGetWideClicks(); /* initialize */
+ }
+ return wideClick.microsecsScale;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclpGetMicroseconds --
+ *
+ * This procedure returns a WideInt value that represents the highest
+ * resolution clock in microseconds available on the system.
+ *
+ * Results:
+ * Number of microseconds (from the epoch).
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_WideInt
+TclpGetMicroseconds(void)
+{
+ Tcl_WideInt usecSincePosixEpoch;
+
+ /* Try to use high resolution timer */
+ if ( tclGetTimeProcPtr == NativeGetTime
+ && (usecSincePosixEpoch = NativeGetMicroseconds())
+ ) {
+ return usecSincePosixEpoch;
+ } else {
+ /*
+ * Use the Tcl_GetTime abstraction to get the time in microseconds, as
+ * nearly as we can, and return it.
+ */
+
+ Tcl_Time now;
+
+ tclGetTimeProcPtr(&now, tclTimeClientData); /* Tcl_GetTime inlined */
+ return (((Tcl_WideInt)now.sec) * 1000000) + now.usec;
+ }
}
/*
@@ -227,7 +381,17 @@ void
Tcl_GetTime(
Tcl_Time *timePtr) /* Location to store time information. */
{
- tclGetTimeProcPtr(timePtr, tclTimeClientData);
+ Tcl_WideInt usecSincePosixEpoch;
+
+ /* Try to use high resolution timer */
+ if ( tclGetTimeProcPtr == NativeGetTime
+ && (usecSincePosixEpoch = NativeGetMicroseconds())
+ ) {
+ timePtr->sec = (long) (usecSincePosixEpoch / 1000000);
+ timePtr->usec = (unsigned long) (usecSincePosixEpoch % 1000000);
+ } else {
+ tclGetTimeProcPtr(timePtr, tclTimeClientData);
+ }
}
/*
@@ -260,13 +424,14 @@ NativeScaleTime(
/*
*----------------------------------------------------------------------
*
- * NativeGetTime --
+ * NativeGetMicroseconds --
*
- * TIP #233: Gets the current system time in seconds and microseconds
- * since the beginning of the epoch: 00:00 UCT, January 1, 1970.
+ * Gets the current system time in microseconds since the beginning
+ * of the epoch: 00:00 UCT, January 1, 1970.
*
* Results:
- * Returns the current time in timePtr.
+ * Returns the wide integer with number of microseconds from the epoch, or
+ * 0 if high resolution timer is not available.
*
* Side effects:
* On the first call, initializes a set of static variables to keep track
@@ -279,13 +444,20 @@ NativeScaleTime(
*----------------------------------------------------------------------
*/
-static void
-NativeGetTime(
- Tcl_Time *timePtr,
- ClientData clientData)
-{
- struct _timeb t;
+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.
*
@@ -296,6 +468,10 @@ NativeGetTime(
if (!timeInfo.initialized) {
TclpInitLock();
if (!timeInfo.initialized) {
+
+ timeInfo.posixEpoch.LowPart = 0xD53E8000;
+ timeInfo.posixEpoch.HighPart = 0x019DB1DE;
+
timeInfo.perfCounterAvailable =
QueryPerformanceFrequency(&timeInfo.nominalFreq);
@@ -400,22 +576,12 @@ NativeGetTime(
* time.
*/
- ULARGE_INTEGER fileTimeLastCall;
- LARGE_INTEGER perfCounterLastCall, curCounterFreq;
+ ULONGLONG fileTimeLastCall;
+ LONGLONG perfCounterLastCall, curCounterFreq;
/* Copy with current data of calibration cycle */
LARGE_INTEGER curCounter;
/* Current performance counter. */
- Tcl_WideInt curFileTime;/* Current estimated time, expressed as 100-ns
- * ticks since the Windows epoch. */
- static LARGE_INTEGER posixEpoch;
- /* Posix epoch expressed as 100-ns ticks since
- * the windows epoch. */
- Tcl_WideInt usecSincePosixEpoch;
- /* Current microseconds since Posix epoch. */
-
- posixEpoch.LowPart = 0xD53E8000;
- posixEpoch.HighPart = 0x019DB1DE;
QueryPerformanceCounter(&curCounter);
@@ -424,21 +590,18 @@ NativeGetTime(
*/
EnterCriticalSection(&timeInfo.cs);
- fileTimeLastCall.QuadPart = timeInfo.fileTimeLastCall.QuadPart;
- perfCounterLastCall.QuadPart = timeInfo.perfCounterLastCall.QuadPart;
- curCounterFreq.QuadPart = timeInfo.curCounterFreq.QuadPart;
+ 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.QuadPart) {
- usecSincePosixEpoch =
- (fileTimeLastCall.QuadPart - posixEpoch.QuadPart) / 10;
- timePtr->sec = (long) (usecSincePosixEpoch / 1000000);
- timePtr->usec = (unsigned long) (usecSincePosixEpoch % 1000000);
- return;
+ if (curCounter.QuadPart <= perfCounterLastCall) {
+ /* Calibrated file-time is saved from posix in 100-ns ticks */
+ return fileTimeLastCall / 10;
}
/*
@@ -451,27 +614,62 @@ NativeGetTime(
* loop should recover.
*/
- if (curCounter.QuadPart - perfCounterLastCall.QuadPart <
- 11 * curCounterFreq.QuadPart / 10
+ if (curCounter.QuadPart - perfCounterLastCall <
+ 11 * curCounterFreq * timeInfo.calibrationInterv / 10
) {
- curFileTime = fileTimeLastCall.QuadPart +
- ((curCounter.QuadPart - perfCounterLastCall.QuadPart)
- * 10000000 / curCounterFreq.QuadPart);
-
- usecSincePosixEpoch = (curFileTime - posixEpoch.QuadPart) / 10;
- timePtr->sec = (long) (usecSincePosixEpoch / 1000000);
- timePtr->usec = (unsigned long) (usecSincePosixEpoch % 1000000);
- return;
+ /* 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. Just use ftime.
+ * High resolution timer is not available.
*/
+ return 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * NativeGetTime --
+ *
+ * TIP #233: Gets the current system time in seconds and microseconds
+ * since the beginning of the epoch: 00:00 UCT, January 1, 1970.
+ *
+ * Results:
+ * Returns the current time in timePtr.
+ *
+ * Side effects:
+ * See NativeGetMicroseconds for more information.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+NativeGetTime(
+ Tcl_Time *timePtr,
+ ClientData clientData)
+{
+ Tcl_WideInt usecSincePosixEpoch;
- _ftime(&t);
- timePtr->sec = (long)t.time;
- timePtr->usec = t.millitm * 1000;
+ /*
+ * Try to use high resolution timer.
+ */
+ if ( (usecSincePosixEpoch = NativeGetMicroseconds()) ) {
+ timePtr->sec = (long) (usecSincePosixEpoch / 1000000);
+ timePtr->usec = (unsigned long) (usecSincePosixEpoch % 1000000);
+ } else {
+ /*
+ * High resolution timer is not available. Just use ftime.
+ */
+
+ struct _timeb t;
+
+ _ftime(&t);
+ timePtr->sec = (long)t.time;
+ timePtr->usec = t.millitm * 1000;
+ }
}
/*
@@ -492,6 +690,8 @@ NativeGetTime(
*----------------------------------------------------------------------
*/
+void TclWinResetTimerResolution(void);
+
static void
StopCalibration(
ClientData unused) /* Client data is unused */
@@ -782,6 +982,8 @@ CalibrationThread(
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,
@@ -841,6 +1043,7 @@ UpdateTimeEachSecond(void)
/* 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. */
@@ -852,15 +1055,24 @@ UpdateTimeEachSecond(void)
* step over 1 second. */
/*
- * Sample performance counter and system time.
+ * Sample performance counter and system time (from posix epoch).
*/
- QueryPerformanceCounter(&curPerfCounter);
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);
- EnterCriticalSection(&timeInfo.cs);
+ lastFileTime.QuadPart = curFileTime.QuadPart;
/*
* We devide by timeInfo.curCounterFreq.QuadPart in several places. That
@@ -872,7 +1084,6 @@ UpdateTimeEachSecond(void)
*/
if (timeInfo.curCounterFreq.QuadPart == 0){
- LeaveCriticalSection(&timeInfo.cs);
timeInfo.perfCounterAvailable = 0;
return;
}
@@ -911,12 +1122,9 @@ UpdateTimeEachSecond(void)
* is estFreq * 20000000 / (vt1 - vt0)
*/
- vt0 = 10000000 * (curPerfCounter.QuadPart
- - timeInfo.perfCounterLastCall.QuadPart)
- / timeInfo.curCounterFreq.QuadPart
- + timeInfo.fileTimeLastCall.QuadPart;
- vt1 = 20000000 + curFileTime.QuadPart;
-
+ 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,
@@ -925,21 +1133,75 @@ UpdateTimeEachSecond(void)
tdiff = vt0 - curFileTime.QuadPart;
if (tdiff > 10000000 || tdiff < -10000000) {
- timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart;
- timeInfo.curCounterFreq.QuadPart = estFreq;
+ /* jump to current system time, use curent estimated frequency */
+ vt0 = curFileTime.QuadPart;
} else {
- driftFreq = estFreq * 20000000 / (vt1 - vt0);
+ /* 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;
+ }
- if (driftFreq > 1003*estFreq/1000) {
- driftFreq = 1003*estFreq/1000;
- } else if (driftFreq < 997*estFreq/1000) {
- driftFreq = 997*estFreq/1000;
+ /* 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);
- timeInfo.fileTimeLastCall.QuadPart = vt0;
- timeInfo.curCounterFreq.QuadPart = driftFreq;
+ /* 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);