summaryrefslogtreecommitdiffstats
path: root/base/time_win.cc
diff options
context:
space:
mode:
Diffstat (limited to 'base/time_win.cc')
-rw-r--r--base/time_win.cc152
1 files changed, 121 insertions, 31 deletions
diff --git a/base/time_win.cc b/base/time_win.cc
index 32f8333..e7d7ae8 100644
--- a/base/time_win.cc
+++ b/base/time_win.cc
@@ -2,6 +2,38 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+
+// Windows Timer Primer
+//
+// A good article: http://www.ddj.com/windows/184416651
+// A good mozilla bug: http://bugzilla.mozilla.org/show_bug.cgi?id=363258
+//
+// The default windows timer, GetSystemTimeAsFileTime is not very precise.
+// It is only good to ~15.5ms.
+//
+// QueryPerformanceCounter is the logical choice for a high-precision timer.
+// However, it is known to be buggy on some hardware. Specifically, it can
+// sometimes "jump". On laptops, QPC can also be very expensive to call.
+// It's 3-4x slower than timeGetTime() on desktops, but can be 10x slower
+// on laptops. A unittest exists which will show the relative cost of various
+// timers on any system.
+//
+// The next logical choice is timeGetTime(). timeGetTime has a precision of
+// 1ms, but only if you call APIs (timeBeginPeriod()) which affect all other
+// applications on the system. By default, precision is only 15.5ms.
+// Unfortunately, we don't want to call timeBeginPeriod because we don't
+// want to affect other applications. Further, on mobile platforms, use of
+// faster multimedia timers can hurt battery life. See the intel
+// article about this here:
+// http://softwarecommunity.intel.com/articles/eng/1086.htm
+//
+// To work around all this, we're going to generally use timeGetTime(). We
+// will only increase the system-wide timer if we're not running on battery
+// power. Using timeBeginPeriod(1) is a requirement in order to make our
+// message loop waits have the same resolution that our time measurements
+// do. Otherwise, WaitForSingleObject(..., 1) will no less than 15ms when
+// there is nothing else to waken the Wait.
+
#include "base/time.h"
#pragma comment(lib, "winmm.lib")
@@ -11,7 +43,9 @@
#include "base/basictypes.h"
#include "base/lock.h"
#include "base/logging.h"
+#include "base/cpu.h"
#include "base/singleton.h"
+#include "base/system_monitor.h"
namespace {
@@ -174,6 +208,7 @@ DWORD timeGetTimeWrapper() {
return timeGetTime();
}
+
DWORD (*tick_function)(void) = &timeGetTimeWrapper;
// We use timeGetTime() to implement TimeTicks::Now(). This can be problematic
@@ -181,20 +216,20 @@ DWORD (*tick_function)(void) = &timeGetTimeWrapper;
// which will roll over the 32-bit value every ~49 days. We try to track
// rollover ourselves, which works if TimeTicks::Now() is called at least every
// 49 days.
-class NowSingleton {
+class NowSingleton : public base::SystemMonitor::PowerObserver {
public:
NowSingleton()
- : rollover_(TimeDelta::FromMilliseconds(0)), last_seen_(0) {
- // Request a resolution of 1ms from timeGetTime(). This can have some
- // consequences on other applications (see MSDN), but we've found that it
- // is very common in other applications (Flash, Media Player, etc), so it
- // is not really a dangerous thing to do. We need this because the default
- // resolution of ~15ms is much too low for accurate timing and timers.
- ::timeBeginPeriod(1);
+ : rollover_(TimeDelta::FromMilliseconds(0)),
+ last_seen_(0),
+ hi_res_clock_enabled_(false) {
+ base::SystemMonitor* system = base::SystemMonitor::Get();
+ system->AddObserver(this);
+ UseHiResClock(!system->BatteryPower());
}
~NowSingleton() {
- ::timeEndPeriod(1);
+ UseHiResClock(false);
+ base::SystemMonitor::Get()->RemoveObserver(this);
}
TimeDelta Now() {
@@ -208,10 +243,30 @@ class NowSingleton {
return TimeDelta::FromMilliseconds(now) + rollover_;
}
+ // Interfaces for monitoring Power changes.
+ void OnPowerStateChange(base::SystemMonitor* system) {
+ UseHiResClock(!system->BatteryPower());
+ }
+
+ void OnSuspend(base::SystemMonitor* system) {}
+ void OnResume(base::SystemMonitor* system) {}
+
private:
+ // Enable or disable the faster multimedia timer.
+ void UseHiResClock(bool enabled) {
+ if (enabled == hi_res_clock_enabled_)
+ return;
+ if (enabled)
+ timeBeginPeriod(1);
+ else
+ timeEndPeriod(1);
+ hi_res_clock_enabled_ = enabled;
+ }
+
Lock lock_; // To protected last_seen_ and rollover_.
TimeDelta rollover_; // Accumulation of time lost due to rollover.
DWORD last_seen_; // The last timeGetTime value we saw, to detect rollover.
+ bool hi_res_clock_enabled_;
DISALLOW_COPY_AND_ASSIGN(NowSingleton);
};
@@ -244,32 +299,75 @@ class NowSingleton {
// (3) System time. The system time provides a low-resolution (typically 10ms
// to 55 milliseconds) time stamp but is comparatively less expensive to
// retrieve and more reliable.
-class UnreliableHighResNowSingleton {
+class HighResNowSingleton {
public:
- UnreliableHighResNowSingleton() : ticks_per_microsecond_(0) {
+ HighResNowSingleton()
+ : ticks_per_microsecond_(0.0),
+ skew_(0) {
+ InitializeClock();
+
+ // On Athlon X2 CPUs (e.g. model 15) QueryPerformanceCounter is
+ // unreliable. Fallback to low-res clock.
+ base::CPU cpu;
+ if (cpu.vendor_name() == "AuthenticAMD" && cpu.family() == 15)
+ DisableHighResClock();
+ }
+
+ bool IsUsingHighResClock() {
+ return ticks_per_microsecond_ != 0.0;
+ }
+
+ void DisableHighResClock() {
+ ticks_per_microsecond_ = 0.0;
+ }
+
+ TimeDelta Now() {
+ // Our maximum tolerance for QPC drifting.
+ const int kMaxTimeDrift = 50 * Time::kMicrosecondsPerMillisecond;
+
+ if (IsUsingHighResClock()) {
+ int64 now = UnreliableNow();
+
+ // Verify that QPC does not seem to drift.
+ DCHECK(now - ReliableNow() - skew_ < kMaxTimeDrift);
+
+ return TimeDelta::FromMicroseconds(now);
+ }
+
+ // Just fallback to the slower clock.
+ return Singleton<NowSingleton>::get()->Now();
+ }
+
+ private:
+ // Synchronize the QPC clock with GetSystemTimeAsFileTime.
+ void InitializeClock() {
LARGE_INTEGER ticks_per_sec = {0};
if (!QueryPerformanceFrequency(&ticks_per_sec))
return; // Broken, we don't guarantee this function works.
- ticks_per_microsecond_ =
- ticks_per_sec.QuadPart / Time::kMicrosecondsPerSecond;
- }
+ ticks_per_microsecond_ = static_cast<float>(ticks_per_sec.QuadPart) /
+ static_cast<float>(Time::kMicrosecondsPerSecond);
- bool IsBroken() {
- return ticks_per_microsecond_ == 0;
+ skew_ = UnreliableNow() - ReliableNow();
}
- TimeDelta Now() {
+ // Get the number of microseconds since boot in a reliable fashion
+ int64 UnreliableNow() {
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
- return TimeDelta::FromMicroseconds(now.QuadPart / ticks_per_microsecond_);
+ return static_cast<int64>(now.QuadPart / ticks_per_microsecond_);
+ }
+
+ // Get the number of microseconds since boot in a reliable fashion
+ int64 ReliableNow() {
+ return Singleton<NowSingleton>::get()->Now().InMicroseconds();
}
- private:
// Cached clock frequency -> microseconds. This assumes that the clock
// frequency is faster than one microsecond (which is 1MHz, should be OK).
- int64 ticks_per_microsecond_; // 0 indicates QPF failed and we're broken.
+ float ticks_per_microsecond_; // 0 indicates QPF failed and we're broken.
+ int64 skew_; // Skew between lo-res and hi-res clocks (for debugging).
- DISALLOW_COPY_AND_ASSIGN(UnreliableHighResNowSingleton);
+ DISALLOW_COPY_AND_ASSIGN(HighResNowSingleton);
};
} // namespace
@@ -288,14 +386,6 @@ TimeTicks TimeTicks::Now() {
}
// static
-TimeTicks TimeTicks::UnreliableHighResNow() {
- UnreliableHighResNowSingleton* now =
- Singleton<UnreliableHighResNowSingleton>::get();
-
- if (now->IsBroken()) {
- NOTREACHED() << "QueryPerformanceCounter is broken.";
- return TimeTicks(0);
- }
-
- return TimeTicks() + now->Now();
+TimeTicks TimeTicks::HighResNow() {
+ return TimeTicks() + Singleton<HighResNowSingleton>::get()->Now();
}