
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Hi, (This is a followup to this question of mine on boost-users http://lists.boost.org/MailArchives/boost-users/msg06618.php . Jeff Garland suggested comments on that thread should be posted here. I'm new to this list, and not entirely sure if messages with this sort of platform-specific detail are welcome here; if they aren't, let me know.) I've been examining possibilities for a high resolution clock in date_time for Win32 over the past few days. In particular, Windows, like many other platforms, has a system clock with only fair precision (usually about 10ms), but excellent 'counter' clocks with very high precision. It seemed reasonable to me that it should be possible to combine the accuracy of the system clock with the precision of a high-resolution counters, to create a Boost date_time clock that had both the system's maximum accuracy and maximum precision. So, after a few days of casual research and brainstorming, I came up with this simple, incomplete prototype clock. The idea is that it queries both the system clock and the high resolution counter, saves them in static memory. Future calls only use the high resolution counter, using the saved correspondence between the two clocks to calculate the real time. This sort of timer, if it can be implemented reasonably, might be useful on many platforms, including when only time() and clock() are availible. This implementation has these characteristics: THEORY (Wishful thinking) - -Should be the best general-purpose clock and timer on Win32, that would be very difficult to exceed in useful precision or accuracy in a reimplementation. - -Should gracefully handle user changes of system clock. PRACTICE - -It does in fact have the promised ten millisecond accuracy and sub-microsecond resolution. - -The Windows system clock is a lot more "jittery" than I had noticed before. This is a huge problem. The clock routine checks every call to ensure that the two clocks are not further apart than twice the reported precision of the system clock. If they are, the saved times are re-initialized, and the clock starts over from the system time. There is a small problem in my implementation that causes the clock to return subsequent decreasing times for only very small drifts, which should not happen. I have not yet decided how to fix this, but this is not the big problem. The big problem is that while the system clock and counter stay very well in synch under 'normal conditions,' the system clock jitters a whole lot when a lot of system resources are being used in the background, causing the clock to reset itself when it really should not. This in turn causes some strange precision characteristics which might be unacceptable for some applications. I'm going to let this sit for a few days while I think about it. I'd be very interested in hearing any comments in the meantime. Aaron W. LaFramboise #include "boost/date_time/c_time.hpp" #include <windows.h> namespace boost { namespace date_time { ~ template<class time_type> ~ class win32_clock ~ { ~ public: ~ typedef typename time_type::date_type date_type; ~ typedef typename time_type::time_duration_type time_duration_type; ~ typedef typename time_duration_type::rep_type resolution_traits_type; ~ //! Return the local time based on computer clock settings ~ static time_type local_time() { ~ SYSTEMTIME st; ~ ::GetSystemTime(&st); ~ LARGE_INTEGER pc; ~ ::QueryPerformanceCounter(&pc); ~ return create_time(st, pc); ~ } ~ private: ~ static time_type create_time(const SYSTEMTIME &system_time, ~ const LARGE_INTEGER &count) ~ { ~ // Suspicious: lock mutex. ~ static long mutex; ~ while(::InterlockedExchange(&mutex, 1)) ~ Sleep(1); ~ // System time and counter at a previous call. ~ static ULONGLONG saved_time; ~ static ULONGLONG saved_count; ~ // Decode system time and get system time precision ~ ULONGLONG now_time; ~ ::SystemTimeToFileTime(&system_time, ~ reinterpret_cast<FILETIME *>(&now_time)); ~ DWORD sysincrement; ~ { ~ DWORD sysadjust; ~ BOOL sysdisabled; ~ ::GetSystemTimeAdjustment(&sysadjust, &sysincrement, &sysdisabled); ~ } ~ // Convert counter, and get frequency. ~ ULONGLONG now_count = count.QuadPart; ~ LONGLONG frequency; ~ ::QueryPerformanceFrequency( ~ reinterpret_cast<LARGE_INTEGER *>(&frequency)); ~ if(frequency <= 0) { ~ frequency = 1; ~ now_count = 0; ~ } ~ // Release mutex. ~ ::InterlockedExchange(&mutex, 0); ~ // Calculate time now (in FILETIME style measurement) according ~ // to the counter, and calculate drift from system clock. ~ double now = static_cast<double>(now_count - saved_count) ~ * 10000000 / frequency + saved_time; ~ double drift = now - now_time; ~ if(drift < 0) ~ drift = -drift; ~ // Initialize static data on first call, or counter drift. ~ // Adjust time now if counter has drifted. ~ if(saved_count == 0 ~ || now_time < saved_time ~ || now_count < saved_count ~ || drift > sysincrement * 2) { ~ if(saved_count != 0) ~ std::cerr << "DRIFT " << now - now_time << std::endl; ~ saved_time = now_time; ~ saved_count = now_count; ~ now = saved_time; ~ } ~ // Now that we've decided what time it is, break it into time units. ~ ULONGLONG now_ull = static_cast<ULONGLONG>(now); ~ SYSTEMTIME now_systemtime; ~ ::FileTimeToSystemTime(reinterpret_cast<FILETIME *>(&now_ull), ~ &now_systemtime); ~ now_systemtime.wMilliseconds = 0; ~ ::SystemTimeToFileTime(&now_systemtime, ~ reinterpret_cast<FILETIME *>(&now_ull)); ~ double frac_seconds = (now - now_ull)/10000000; ~ // Create date_now and time_duration_now. ~ date_type date_now(now_systemtime.wYear, now_systemtime.wMonth, ~ now_systemtime.wDay); ~ time_duration_type time_duration_now(now_systemtime.wHour, ~ now_systemtime.wMinute, now_systemtime.wSecond, ~ static_cast<ULONGLONG>(frac_seconds ~ * resolution_traits_type::res_adjust())); ~ return time_type(date_now, time_duration_now); ~ } ~ }; } } //namespace date_time #include <iostream> // #include "clock.hpp" #include "boost/date_time/posix_time/posix_time.hpp" using boost::posix_time::ptime; ptime now() { ~ return boost::date_time::win32_clock<ptime>::local_time(); } int main() { ~ using boost::posix_time::seconds; ~ ptime start = now(); ~ ptime last = start; ~ ptime n = last; ~ do { ~ n = now(); ~ if(n == last) ~ std::cerr << "two times equal!\n"; ~ if(n < last) ~ std::cerr << "time went backwards: " << n - last << '\n'; ~ last = n; ~ } while(n < start + seconds(15)); ~ std::cout << n - start << '\n'; ~ return 0; } -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.2.4 (MingW32) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFAnX8tlp/lhnWsM8QRAjAdAJoDKMO0++1NeO4CMqaY9K+Q/VhvKgCdGS7x aOARRMWjmfr3KrMiDsNH7qk= =M9Kf -----END PGP SIGNATURE-----