ANNOUNCE: system-time-monotonic-0.1

system-time-monotonic [1] provides access to the system's monotonic clock. Usage looks like this: * Use 'newClock' to create a new monotonic clock * Use 'clockGetTime' to see how much time has elapsed since the clock was created. This package currently supports Linux and Windows. It might also work on BSD, but I haven't tested it there. Mac OS X support is currently not implemented, but patches are welcome. GHC uses mach_absolute_time and mach_timebase_info; see ticket #5865 [2]. I also added a handy utility function 'delay', a variant of threadDelay taking a DiffTime instead of Int microseconds. Thus: delay 1.5 Waits 1.5 seconds. Note that since 'delay' simply calls 'threadDelay' in a loop, it is disrupted by system clock changes (again, see ticket #5865). [1]: http://hackage.haskell.org/package/system-time-monotonic [2]: http://hackage.haskell.org/trac/ghc/ticket/5865 --- The rest of this email describes various hurdles involved in implementing this package, and how they were addressed. ## GetTickCount The most obvious one is that GetTickCount has a short wraparound (49.7 days). Two ways to address this: * Don't use GetTickCount; use QueryPerformanceCounter (or similar) instead. This is currently how it's done in GHC HEAD. * Use GetTickCount, but avoid comparing times that are far apart by tracking the total difference each time we look at the clock. I took the second approach, because I found out that QueryPerformanceCounter is actually less accurate in the long run than GetTickCount. In particular, QueryPerformanceCounter stops ticking (or maybe ticks slower, I forget) when the computer is asleep. Here's the trick I use to work around GetTickCount's wraparound (pseudocode): st1 :: Word32 t1 :: DiffTime newClock: st1 := GetTickCount() t1 := 0 clockGetTime: st2 := GetTickCount() t2 := t1 + (Int32)(st2 - st1 modulo 2^32) st1 := st2 t1 := t2 return t2 This workaround only works if clockGetTime is called at least once every 24.8 days. It's important that st2 - st1 be done modulo 2^32, to compensate for wraparound. However, the result should be converted to a signed 32-bit integer, in case st2 was recorded earlier than st1 (which can easily happen in a concurrent context). In particular, here's what you should *not* do (which GHC currently does, when QueryPerformanceCounter is not available): st1 = (bigger_type) GetTickCount(); ... st2 = (bigger_type) GetTickCount(); return (st2 - st1) This will return a bogus value if a wraparound occurred between st1 and st2. system-time-monotonic tests, at runtime, if GetTickCount64 is available. If so, it uses it. Otherwise, it falls back to GetTickCount. Here's the code I used to do the run-time system call lookup: /* cbits/dll.c */ typedef ULONGLONG (WINAPI *GetTickCount64_t)(void); GetTickCount64_t system_time_monotonic_load_GetTickCount64(void) { return (GetTickCount64_t) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetTickCount64"); } -- System/Time/Monotonic/Direct.hsc type C_GetTickCount64 = IO #{type ULONGLONG} foreign import ccall system_time_monotonic_load_GetTickCount64 :: IO (FunPtr C_GetTickCount64) foreign import stdcall "dynamic" mkGetTickCount64 :: FunPtr C_GetTickCount64 -> C_GetTickCount64 Did I do this right? In particular: * Can I assume the kernel32.dll HMODULE won't be pulled out from under me? * Is `foreign import stdcall "dynamic"` the right incantation for using a pointer to a WINAPI function? ## CLOCK_MONOTONIC is not actually monotonic on Linux CLOCK_MONOTONIC is subject to NTP adjustments. Worse, CLOCK_MONOTONIC stops when the computer is put to sleep (unlike GetTickCount, which does the right thing). Two clocks were introduced very recently in Linux: * CLOCK_MONOTONIC_RAW * CLOCK_BOOTTIME I'd like to support CLOCK_BOOTTIME at some point. I'm not sure if it's subject to NTP adjustments or not, since the announcement [3] says: CLOCK_BOOTTIME is identical to CLOCK_MONOTONIC, except it also includes any time spent in suspend (as currently measured by read_persistent_clock()). This allows applications to get a suspend aware monotonic clock. One idea would be to hard-code the clockid_t of CLOCK_BOOTTIME and CLOCK_MONOTONIC_RAW, and try calling clock_gettime with each of these. If one of these succeeds, use it. Otherwise, fall back to CLOCK_MONOTONIC. Thus, the compiled binary can test, at runtime, if a new enough kernel is available. For now, system-time-monotonic simply uses CLOCK_MONOTONIC. [3]: http://lwn.net/Articles/428176/
participants (1)
-
Joey Adams