summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
Diffstat (limited to 'base')
-rw-r--r--base/time.h12
-rw-r--r--base/time_posix.cc2
-rw-r--r--base/time_unittest.cc39
-rw-r--r--base/time_unittest_win.cc92
-rw-r--r--base/time_win.cc152
-rw-r--r--base/trace_event.cc2
6 files changed, 248 insertions, 51 deletions
diff --git a/base/time.h b/base/time.h
index addb246..4141116 100644
--- a/base/time.h
+++ b/base/time.h
@@ -348,15 +348,15 @@ class TimeTicks {
}
// Platform-dependent tick count representing "right now."
- // The resolution of this clock is ~1-5ms. Resolution varies depending
+ // The resolution of this clock is ~1-15ms. Resolution varies depending
// on hardware/operating system configuration.
static TimeTicks Now();
- // Returns a platform-dependent high-resolution tick count. IT IS BROKEN ON
- // SOME HARDWARE and is designed to be used for profiling and perf testing
- // only (see the impl for more information).
- static TimeTicks UnreliableHighResNow();
-
+ // Returns a platform-dependent high-resolution tick count. Implementation
+ // is hardware dependent and may or may not return sub-millisecond
+ // resolution. THIS CALL IS GENERALLY MUCH MORE EXPENSIVE THAN Now() AND
+ // SHOULD ONLY BE USED WHEN IT IS REALLY NEEDED.
+ static TimeTicks HighResNow();
// Returns true if this object has not been initialized.
bool is_null() const {
diff --git a/base/time_posix.cc b/base/time_posix.cc
index 88c49a2..18dc240 100644
--- a/base/time_posix.cc
+++ b/base/time_posix.cc
@@ -137,6 +137,6 @@ TimeTicks TimeTicks::Now() {
}
// static
-TimeTicks TimeTicks::UnreliableHighResNow() {
+TimeTicks TimeTicks::HighResNow() {
return Now();
}
diff --git a/base/time_unittest.cc b/base/time_unittest.cc
index 039913b..db0990f4 100644
--- a/base/time_unittest.cc
+++ b/base/time_unittest.cc
@@ -86,21 +86,38 @@ TEST(Time, LocalMidnight) {
}
TEST(TimeTicks, Deltas) {
- TimeTicks ticks_start = TimeTicks::Now();
- PlatformThread::Sleep(10);
- TimeTicks ticks_stop = TimeTicks::Now();
- TimeDelta delta = ticks_stop - ticks_start;
- EXPECT_GE(delta.InMilliseconds(), 10);
- EXPECT_GE(delta.InMicroseconds(), 10000);
- EXPECT_EQ(delta.InSeconds(), 0);
+ for (int index = 0; index < 500; index++) {
+ TimeTicks ticks_start = TimeTicks::Now();
+ PlatformThread::Sleep(10);
+ TimeTicks ticks_stop = TimeTicks::Now();
+ TimeDelta delta = ticks_stop - ticks_start;
+ // Note: Although we asked for a 10ms sleep, if the
+ // time clock has a finer granularity than the Sleep()
+ // clock, it is quite possible to wakeup early. Here
+ // is how that works:
+ // Time(ms timer) Time(us timer)
+ // 5 5010
+ // 6 6010
+ // 7 7010
+ // 8 8010
+ // 9 9000
+ // Elapsed 4ms 3990us
+ //
+ // Unfortunately, our InMilliseconds() function truncates
+ // rather than rounds. We should consider fixing this
+ // so that our averages come out better.
+ EXPECT_GE(delta.InMilliseconds(), 9);
+ EXPECT_GE(delta.InMicroseconds(), 9000);
+ EXPECT_EQ(delta.InSeconds(), 0);
+ }
}
-TEST(TimeTicks, UnreliableHighResNow) {
- TimeTicks ticks_start = TimeTicks::UnreliableHighResNow();
+TEST(TimeTicks, HighResNow) {
+ TimeTicks ticks_start = TimeTicks::HighResNow();
PlatformThread::Sleep(10);
- TimeTicks ticks_stop = TimeTicks::UnreliableHighResNow();
+ TimeTicks ticks_stop = TimeTicks::HighResNow();
TimeDelta delta = ticks_stop - ticks_start;
- EXPECT_GE(delta.InMilliseconds(), 10);
+ EXPECT_GE(delta.InMicroseconds(), 9000);
}
TEST(TimeDelta, FromAndIn) {
diff --git a/base/time_unittest_win.cc b/base/time_unittest_win.cc
index 87cf698..4a7120e 100644
--- a/base/time_unittest_win.cc
+++ b/base/time_unittest_win.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include <windows.h>
+#include <mmsystem.h>
#include <process.h>
#include "base/time.h"
@@ -44,7 +45,9 @@ unsigned __stdcall RolloverTestThreadMain(void* param) {
for (int index = 0; index < counter; index++) {
TimeTicks now = TimeTicks::Now();
int64 milliseconds = (now - last).InMilliseconds();
- EXPECT_GT(milliseconds, 0);
+ // This is a tight loop; we could have looped faster than our
+ // measurements, so the time might be 0 millis.
+ EXPECT_GE(milliseconds, 0);
EXPECT_LT(milliseconds, 250);
last = now;
}
@@ -99,3 +102,90 @@ TEST(TimeTicks, WinRollover) {
MockTimeTicks::UninstallTicker();
}
}
+
+TEST(TimeTicks, SubMillisecondTimers) {
+ // Loop for a bit getting timers quickly. We want to
+ // see at least one case where we get a new sample in
+ // less than one millisecond.
+ bool saw_submillisecond_timer = false;
+ int64 min_timer = 1000;
+ TimeTicks last_time = TimeTicks::HighResNow();
+ for (int index = 0; index < 1000; index++) {
+ TimeTicks now = TimeTicks::HighResNow();
+ TimeDelta delta = now - last_time;
+ if (delta.InMicroseconds() > 0 &&
+ delta.InMicroseconds() < 1000) {
+ if (min_timer > delta.InMicroseconds())
+ min_timer = delta.InMicroseconds();
+ saw_submillisecond_timer = true;
+ }
+ last_time = now;
+ }
+ EXPECT_TRUE(saw_submillisecond_timer);
+ printf("Min timer is: %dus\n", min_timer);
+}
+
+TEST(TimeTicks, TimeGetTimeCaps) {
+ // Test some basic assumptions that we expect about how timeGetDevCaps works.
+
+ TIMECAPS caps;
+ MMRESULT status = timeGetDevCaps(&caps, sizeof(caps));
+ EXPECT_EQ(TIMERR_NOERROR, status);
+ if (status != TIMERR_NOERROR) {
+ printf("Could not get timeGetDevCaps\n");
+ return;
+ }
+
+ EXPECT_GE(static_cast<int>(caps.wPeriodMin), 1);
+ EXPECT_GT(static_cast<int>(caps.wPeriodMax), 1);
+ EXPECT_GE(static_cast<int>(caps.wPeriodMin), 1);
+ EXPECT_GT(static_cast<int>(caps.wPeriodMax), 1);
+ printf("timeGetTime range is %d to %dms\n", caps.wPeriodMin,
+ caps.wPeriodMax);
+}
+
+TEST(TimeTicks, QueryPerformanceFrequency) {
+ // Test some basic assumptions that we expect about QPC.
+
+ LARGE_INTEGER frequency;
+ BOOL rv = QueryPerformanceFrequency(&frequency);
+ EXPECT_EQ(TRUE, rv);
+ EXPECT_GT(frequency.QuadPart, 1000000); // Expect at least 1MHz
+ printf("QueryPerformanceFrequency is %5.2fMHz\n",
+ frequency.QuadPart / 1000000.0);
+}
+
+TEST(TimeTicks, TimerPerformance) {
+ // Verify that various timer mechanisms can always complete quickly.
+ // Note: This is a somewhat arbitrary test.
+ const int kLoops = 10000;
+ const int kMaxTime = 10; // Maximum acceptible milliseconds for test.
+
+ typedef TimeTicks (*TestFunc)();
+ struct TestCase {
+ TestFunc func;
+ char *description;
+ };
+ // Cheating a bit here: assumes sizeof(TimeTicks) == sizeof(Time)
+ // in order to create a single test case list.
+ COMPILE_ASSERT(sizeof(TimeTicks) == sizeof(Time),
+ test_only_works_with_same_sizes);
+ TestCase cases[] = {
+ { reinterpret_cast<TestFunc>(Time::Now), "Time::Now" },
+ { TimeTicks::Now, "TimeTicks::Now" },
+ { TimeTicks::HighResNow, "TimeTicks::HighResNow" },
+ { NULL, "" }
+ };
+
+ int test_case = 0;
+ while (cases[test_case].func) {
+ TimeTicks start = TimeTicks::HighResNow();
+ for (int index = 0; index < kLoops; index++)
+ cases[test_case].func();
+ TimeTicks stop = TimeTicks::HighResNow();
+ EXPECT_LT((stop - start).InMilliseconds(), kMaxTime);
+ printf("%s: %1.2fus per call\n", cases[test_case].description,
+ (stop - start).InMillisecondsF() * 1000 / kLoops);
+ test_case++;
+ }
+}
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();
}
diff --git a/base/trace_event.cc b/base/trace_event.cc
index 4ed41d5..92939d5 100644
--- a/base/trace_event.cc
+++ b/base/trace_event.cc
@@ -136,7 +136,7 @@ void TraceLog::Trace(const std::string& name,
return;
#ifdef USE_UNRELIABLE_NOW
- TimeTicks tick = TimeTicks::UnreliableHighResNow();
+ TimeTicks tick = TimeTicks::HighResNow();
#else
TimeTicks tick = TimeTicks::Now();
#endif