[MLton-commit] r7217
Wesley Terpstra
wesley at mlton.org
Tue Jul 21 16:28:50 PDT 2009
Make the requirements for gettimeofday explicit:
500,000 years from now the function still works
a program can run for up to 100 years without a restart.
I went through all the conversions to confirm they meet the requirements.
This means handling wrap around of QueryPerformanceCounter if the frequency
is so high that a wrap around before 100 years can happen.
----------------------------------------------------------------------
U mlton/trunk/runtime/platform/mingw.c
----------------------------------------------------------------------
Modified: mlton/trunk/runtime/platform/mingw.c
===================================================================
--- mlton/trunk/runtime/platform/mingw.c 2009-07-11 20:08:21 UTC (rev 7216)
+++ mlton/trunk/runtime/platform/mingw.c 2009-07-21 23:28:49 UTC (rev 7217)
@@ -73,31 +73,34 @@
#define EPOCHFILETIME (116444736000000000LL)
#endif
-/* Based on notes by Wu Yongwei and IBM:
- * http://mywebpage.netscape.com/yongweiwutime.htm
- * http://www.ibm.com/developerworks/library/i-seconds/
- *
- * The basic plan is to get an initial time using GetSystemTime
+/* The basic plan is to get an initial time using GetSystemTime
* that is good up to ~10ms accuracy. From then on, we compute
* using deltas with the high-resolution (> microsecond range)
* performance timers. A 64-bit accumulator holds microseconds
* since (*nix) epoch. This is good for over 500,000 years before
- * wrap-around becomes a concern. However, we do need to watch
- * out for wrap-around with the QueryPerformanceCounter, because
- * it could be measuring at a higher frequency than microseconds.
+ * wrap-around becomes a concern.
+ *
+ * However, we might need to watch out for wrap-around with the
+ * QueryPerformanceCounter, because it could be measuring at a higher
+ * frequency than microseconds.
+ *
+ * This function only strives to allow a program to run for
+ * 100 years without being restarted.
*/
int gettimeofday (struct timeval *tv,
__attribute__ ((unused)) struct timezone *tz) {
- static LARGE_INTEGER frequency;
- static LARGE_INTEGER baseCounter;
- static LARGE_INTEGER microSeconds; /* static vars start = 0 */
+ static LARGE_INTEGER frequency; /* ticks/second */
+ static LARGE_INTEGER baseCounter; /* ticks since last rebase */
+ static LARGE_INTEGER baseMicroSeconds; /* unix time at last rebase */
+ LARGE_INTEGER nowCounter;
LARGE_INTEGER deltaCounter;
LARGE_INTEGER nowMicroSeconds;
+ double deltaMicroseconds;
- if (microSeconds.QuadPart == 0) {
+ /* This code is run the first time gettimeofday is called. */
+ if (frequency.QuadPart == 0) {
FILETIME ft;
-
/* tzset prepares the localtime function. I don't
* really understand why it's here and not there,
* but this has been the case since before svn logs.
@@ -105,37 +108,72 @@
*/
tzset();
- GetSystemTimeAsFileTime (&ft);
QueryPerformanceCounter(&baseCounter);
QueryPerformanceFrequency(&frequency);
if (frequency.QuadPart == 0)
die("no high resolution clock");
- microSeconds.LowPart = ft.dwLowDateTime;
- microSeconds.HighPart = ft.dwHighDateTime;
- microSeconds.QuadPart -= EPOCHFILETIME;
- microSeconds.QuadPart /= 10; /* 100ns -> 1ms */
+ GetSystemTimeAsFileTime (&ft);
+ baseMicroSeconds.LowPart = ft.dwLowDateTime;
+ baseMicroSeconds.HighPart = ft.dwHighDateTime;
+ baseMicroSeconds.QuadPart -= EPOCHFILETIME;
+ baseMicroSeconds.QuadPart /= 10; /* 100ns -> 1ms */
}
-
- QueryPerformanceCounter(&deltaCounter);
- deltaCounter.QuadPart -= baseCounter.QuadPart;
- nowMicroSeconds = microSeconds;
- nowMicroSeconds.QuadPart +=
- 1000000 * deltaCounter.QuadPart / frequency.QuadPart;
-
+
+ /* Use the high res counter ticks to calculate the delta.
+ * A double has 52+1 bits of precision. This means it can fit
+ * deltas of up to 9007199254 seconds, or 286 years. We could
+ * rebase before an overflow, but 286 is already > 100.
+ */
+ QueryPerformanceCounter(&nowCounter);
+ deltaCounter.QuadPart = nowCounter.QuadPart - baseCounter.QuadPart;
+ deltaMicroseconds = deltaCounter.QuadPart;
+ deltaMicroseconds /= frequency.QuadPart;
+ deltaMicroseconds *= 1000000.0;
+ nowMicroSeconds.QuadPart =
+ baseMicroSeconds.QuadPart + deltaMicroseconds;
+
+ /* If the frequency too fast, we need to check for wrap around.
+ * 2**32 seconds is 136 years, so if HighPart == 0 we don't need to
+ * waste a system call on GetSystemTimeAsFileTime.
+ */
+ if (frequency.HighPart != 0) {
+ LARGE_INTEGER nowLowResMicroSeconds;
+ FILETIME ft;
+
+ /* Use low res timer to detect performance counter wrap-around. */
+ GetSystemTimeAsFileTime (&ft);
+ nowLowResMicroSeconds.LowPart = ft.dwLowDateTime;
+ nowLowResMicroSeconds.HighPart = ft.dwHighDateTime;
+ nowLowResMicroSeconds.QuadPart -= EPOCHFILETIME;
+ nowLowResMicroSeconds.QuadPart /= 10;
+
+ /* If deltaMicroseconds deviates by more than a second from the low
+ * resolution timer, assume the high performance counter has wrapped.
+ * One second is a safe margin b/c QueryPerformanceFrequency must fit
+ * in a 64-bit integer. Therefore any wrap must exceed one second.
+ */
+ if (nowMicroSeconds.QuadPart + 1000000 < nowLowResMicroSeconds.QuadPart) {
+ baseCounter = nowCounter;
+ baseMicroSeconds = nowLowResMicroSeconds;
+ nowMicroSeconds = nowLowResMicroSeconds;
+ }
+
+ /* The above wrap-around detection destroys high resolution timing.
+ * However, if one needs high resolution timing, then one is querying
+ * gettimeofday quite often. Therefore, rebase the clock before any
+ * wrap around troubles happen. We don't do this too often as it
+ * introduces clock drift.
+ */
+ if ((deltaCounter.HighPart & 0xffff0000UL) != 0) {
+ baseCounter = nowCounter;
+ baseMicroSeconds = nowMicroSeconds;
+ }
+ }
+
tv->tv_sec = (long)(nowMicroSeconds.QuadPart / 1000000);
tv->tv_usec = (long)(nowMicroSeconds.QuadPart % 1000000);
-
- /* Watch out for wrap-around in the PerformanceCounter.
- * We expect the delta * 1000000 to fit inside a 64 bit integer.
- * To be safe, we will rebase the clock whenever it exceeds 32 bits.
- * We don't want to rebase all the time because it introduces drift.
- */
- if (nowMicroSeconds.HighPart != 0) {
- microSeconds = nowMicroSeconds;
- baseCounter.QuadPart += deltaCounter.QuadPart;
- }
-
+
return 0;
}
More information about the MLton-commit
mailing list