Boost logo

Boost :

From: Aaron W. LaFramboise (aaronrabiddog51_at_[hidden])
Date: 2004-05-08 19:45:34


-----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-----


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk