Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3055)

Unified Diff: base/time/time_win.cc

Issue 797893003: [Windows] One TimeTicks clock: efficient/reliable high-res, with low-res fallback. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Better testing of TimeTicks::FromQPCValue(). Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: base/time/time_win.cc
diff --git a/base/time/time_win.cc b/base/time/time_win.cc
index 9b4f17d6613c7e60b8576adc9ae3b2a6afc8c8a3..8ae76404f0a3c1b42aeb71545758de2892b1dd5f 100644
--- a/base/time/time_win.cc
+++ b/base/time/time_win.cc
@@ -307,13 +307,13 @@ DWORD timeGetTimeWrapper() {
return timeGetTime();
}
-DWORD (*tick_function)(void) = &timeGetTimeWrapper;
+DWORD (*g_tick_function)(void) = &timeGetTimeWrapper;
// Accumulation of time lost due to rollover (in milliseconds).
-int64 rollover_ms = 0;
+int64 g_rollover_ms = 0;
// The last timeGetTime value we saw, to detect rollover.
-DWORD last_seen_now = 0;
+DWORD g_last_seen_now = 0;
// Lock protecting rollover_ms and last_seen_now.
// Note: this is a global object, and we usually avoid these. However, the time
@@ -321,169 +321,161 @@ DWORD last_seen_now = 0;
// easy to use a Singleton without even knowing it, and that may lead to many
// gotchas). Its impact on startup time should be negligible due to low-level
// nature of time code.
-base::Lock rollover_lock;
+base::Lock g_rollover_lock;
// We use timeGetTime() to implement TimeTicks::Now(). This can be problematic
// because it returns the number of milliseconds since Windows has started,
// 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.
-TimeDelta RolloverProtectedNow() {
- base::AutoLock locked(rollover_lock);
+TimeTicks RolloverProtectedNow() {
+ base::AutoLock locked(g_rollover_lock);
// We should hold the lock while calling tick_function to make sure that
// we keep last_seen_now stay correctly in sync.
- DWORD now = tick_function();
- if (now < last_seen_now)
- rollover_ms += 0x100000000I64; // ~49.7 days.
- last_seen_now = now;
- return TimeDelta::FromMilliseconds(now + rollover_ms);
+ DWORD now = g_tick_function();
+ if (now < g_last_seen_now)
+ g_rollover_ms += 0x100000000I64; // ~49.7 days.
+ g_last_seen_now = now;
+ return TimeTicks() + TimeDelta::FromMilliseconds(now + g_rollover_ms);
}
-bool IsBuggyAthlon(const base::CPU& cpu) {
- // On Athlon X2 CPUs (e.g. model 15) QueryPerformanceCounter is
- // unreliable. Fallback to low-res clock.
- return cpu.vendor_name() == "AuthenticAMD" && cpu.family() == 15;
-}
-
-// Overview of time counters:
+// Discussion of tick counter options on Windows:
+//
// (1) CPU cycle counter. (Retrieved via RDTSC)
// The CPU counter provides the highest resolution time stamp and is the least
-// expensive to retrieve. However, the CPU counter is unreliable and should not
-// be used in production. Its biggest issue is that it is per processor and it
-// is not synchronized between processors. Also, on some computers, the counters
-// will change frequency due to thermal and power changes, and stop in some
-// states.
+// expensive to retrieve. However, on older CPUs, two issues can affect its
+// reliability: First it is maintained per processor and not synchronized
+// between processors. Also, the counters will change frequency due to thermal
+// and power changes, and stop in some states.
//
// (2) QueryPerformanceCounter (QPC). The QPC counter provides a high-
-// resolution (100 nanoseconds) time stamp but is comparatively more expensive
-// to retrieve. What QueryPerformanceCounter actually does is up to the HAL.
-// (with some help from ACPI).
-// According to http://blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx
-// in the worst case, it gets the counter from the rollover interrupt on the
+// resolution (<1 microsecond) time stamp. On most hardware running today, it
+// auto-detects and uses the constant-rate RDTSC counter to provide extremely
+// efficient and reliable time stamps.
+//
+// On older CPUs where RDTSC is unreliable, it falls back to using more
+// expensive (20X to 40X more costly) alternate clocks, such as HPET or the ACPI
+// PM timer, and can involve system calls; and all this is up to the HAL (with
+// some help from ACPI). According to
+// http://blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx, in the
+// worst case, it gets the counter from the rollover interrupt on the
// programmable interrupt timer. In best cases, the HAL may conclude that the
// RDTSC counter runs at a constant frequency, then it uses that instead. On
// multiprocessor machines, it will try to verify the values returned from
// RDTSC on each processor are consistent with each other, and apply a handful
// of workarounds for known buggy hardware. In other words, QPC is supposed to
-// give consistent result on a multiprocessor computer, but it is unreliable in
-// reality due to bugs in BIOS or HAL on some, especially old computers.
-// With recent updates on HAL and newer BIOS, QPC is getting more reliable but
-// it should be used with caution.
+// give consistent results on a multiprocessor computer, but for older CPUs it
+// can be unreliable due bugs in BIOS or HAL.
//
-// (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 HighResNowSingleton {
- public:
- HighResNowSingleton()
- : ticks_per_second_(0),
- skew_(0) {
-
- base::CPU cpu;
- if (IsBuggyAthlon(cpu))
- return;
-
- // Synchronize the QPC clock with GetSystemTimeAsFileTime.
- LARGE_INTEGER ticks_per_sec = {0};
- if (!QueryPerformanceFrequency(&ticks_per_sec))
- return; // QPC is not available.
- ticks_per_second_ = ticks_per_sec.QuadPart;
-
- skew_ = UnreliableNow() - ReliableNow();
- }
-
- bool IsUsingHighResClock() {
- return ticks_per_second_ != 0;
- }
-
- TimeDelta Now() {
- if (IsUsingHighResClock())
- return TimeDelta::FromMicroseconds(UnreliableNow());
-
- // Just fallback to the slower clock.
- return RolloverProtectedNow();
+// (3) System time. The system time provides a low-resolution (from ~1 to ~15.6
+// milliseconds) time stamp but is comparatively less expensive to retrieve and
+// more reliable. Time::EnableHighResolutionTimer() and
+// Time::ActivateHighResolutionTimer() can be called to alter the resolution of
+// this timer; and also other Windows applications can alter it, affecting this
+// one.
+
+using NowFunction = TimeTicks (*)(void);
+
+TimeTicks InitialNowFunction();
+TimeTicks InitialSystemTraceNowFunction();
+
+// See "threading notes" in InitializeNowFunctionPointers() for details on how
+// concurrent reads/writes to these globals has been made safe.
+NowFunction g_now_function = &InitialNowFunction;
+NowFunction g_system_trace_now_function = &InitialSystemTraceNowFunction;
+int64 g_qpc_ticks_per_second = 0;
+
+// As of January 2015, use of <atomic> is forbidden in Chromium code. This is
+// what std::atomic_thread_fence does on Windows on all Intel architectures when
+// the memory_order argument is anything but std::memory_order_seq_cst:
+#define ATOMIC_THREAD_FENCE(memory_order) _ReadWriteBarrier();
+
+TimeDelta QPCValueToTimeDelta(LONGLONG qpc_value) {
+ // Ensure that the assignment to |g_qpc_ticks_per_second|, made in
+ // InitializeNowFunctionPointers(), has happened by this point.
+ ATOMIC_THREAD_FENCE(memory_order_acquire);
+
+ DCHECK_GT(g_qpc_ticks_per_second, 0);
+
+ // If the QPC Value is below the overflow threshold, we proceed with
+ // simple multiply and divide.
+ if (qpc_value < Time::kQPCOverflowThreshold) {
+ return TimeDelta::FromMicroseconds(
+ qpc_value * Time::kMicrosecondsPerSecond / g_qpc_ticks_per_second);
}
+ // Otherwise, calculate microseconds in a round about manner to avoid
+ // overflow and precision issues.
+ int64 whole_seconds = qpc_value / g_qpc_ticks_per_second;
+ int64 leftover_ticks = qpc_value - (whole_seconds * g_qpc_ticks_per_second);
+ return TimeDelta::FromMicroseconds(
+ (whole_seconds * Time::kMicrosecondsPerSecond) +
+ ((leftover_ticks * Time::kMicrosecondsPerSecond) /
+ g_qpc_ticks_per_second));
+}
- int64 GetQPCDriftMicroseconds() {
- if (!IsUsingHighResClock())
- return 0;
- return abs((UnreliableNow() - ReliableNow()) - skew_);
- }
-
- int64 QPCValueToMicroseconds(LONGLONG qpc_value) {
- if (!ticks_per_second_)
- return 0;
- // If the QPC Value is below the overflow threshold, we proceed with
- // simple multiply and divide.
- if (qpc_value < Time::kQPCOverflowThreshold)
- return qpc_value * Time::kMicrosecondsPerSecond / ticks_per_second_;
- // Otherwise, calculate microseconds in a round about manner to avoid
- // overflow and precision issues.
- int64 whole_seconds = qpc_value / ticks_per_second_;
- int64 leftover_ticks = qpc_value - (whole_seconds * ticks_per_second_);
- int64 microseconds = (whole_seconds * Time::kMicrosecondsPerSecond) +
- ((leftover_ticks * Time::kMicrosecondsPerSecond) /
- ticks_per_second_);
- return microseconds;
- }
-
- private:
- // Get the number of microseconds since boot in an unreliable fashion.
- int64 UnreliableNow() {
- LARGE_INTEGER now;
- QueryPerformanceCounter(&now);
- return QPCValueToMicroseconds(now.QuadPart);
- }
-
- // Get the number of microseconds since boot in a reliable fashion.
- int64 ReliableNow() {
- return RolloverProtectedNow().InMicroseconds();
- }
-
- int64 ticks_per_second_; // 0 indicates QPF failed and we're broken.
- int64 skew_; // Skew between lo-res and hi-res clocks (for debugging).
-};
-
-static base::LazyInstance<HighResNowSingleton>::Leaky
- leaky_high_res_now_singleton = LAZY_INSTANCE_INITIALIZER;
-
-HighResNowSingleton* GetHighResNowSingleton() {
- return leaky_high_res_now_singleton.Pointer();
+TimeTicks QPCNow() {
+ LARGE_INTEGER now;
+ QueryPerformanceCounter(&now);
+ return TimeTicks() + QPCValueToTimeDelta(now.QuadPart);
}
-TimeDelta HighResNowWrapper() {
- return GetHighResNowSingleton()->Now();
+bool IsBuggyAthlon(const base::CPU& cpu) {
+ // On Athlon X2 CPUs (e.g. model 15) QueryPerformanceCounter is unreliable.
+ return cpu.vendor_name() == "AuthenticAMD" && cpu.family() == 15;
}
-typedef TimeDelta (*NowFunction)(void);
+void InitializeNowFunctionPointers() {
+ LARGE_INTEGER ticks_per_sec = {0};
+ if (!QueryPerformanceFrequency(&ticks_per_sec))
+ ticks_per_sec.QuadPart = 0;
-bool CPUReliablySupportsHighResTime() {
+ // If Windows cannot provide a QPC implementation, both Now() and
+ // NowFromSystemTraceTime() must use the low-resolution clock.
+ //
+ // If the QPC implementation is expensive and/or unreliable, Now() will use
+ // the low-resolution clock, but NowFromSystemTraceTime() will use the QPC (in
+ // the hope that it is still useful for tracing purposes). A CPU lacking a
+ // non-stop time counter will cause Windows to provide an alternate QPC
+ // implementation that works, but is expensive to use. Certain Athlon CPUs are
+ // known to make the QPC implementation unreliable.
+ //
+ // Otherwise, both Now functions can use the high-resolution QPC clock. As of
+ // 4 January 2015, ~68% of users fall within this category.
+ NowFunction now_function;
+ NowFunction system_trace_now_function;
base::CPU cpu;
- if (!cpu.has_non_stop_time_stamp_counter() ||
- !GetHighResNowSingleton()->IsUsingHighResClock())
- return false;
-
- if (IsBuggyAthlon(cpu))
- return false;
+ if (ticks_per_sec.QuadPart <= 0) {
+ now_function = system_trace_now_function = &RolloverProtectedNow;
+ } else if (!cpu.has_non_stop_time_stamp_counter() || IsBuggyAthlon(cpu)) {
+ now_function = &RolloverProtectedNow;
+ system_trace_now_function = &QPCNow;
+ } else {
+ now_function = system_trace_now_function = &QPCNow;
+ }
- return true;
+ // Threading note 1: In an unlikely race condition, it's possible for two or
+ // more threads to enter InitializeNowFunctionPointers() in parallel. This is
+ // not a problem since all threads should end up writing out the same values
+ // to the global variables.
+ //
+ // Threading note 2: A release fence is placed here to ensure, from the
+ // perspective of other threads using the function pointers, that the
+ // assignment to |g_qpc_ticks_per_second| happens before the function pointers
+ // are changed.
+ g_qpc_ticks_per_second = ticks_per_sec.QuadPart;
+ ATOMIC_THREAD_FENCE(memory_order_release);
+ g_now_function = now_function;
+ g_system_trace_now_function = system_trace_now_function;
}
-TimeDelta InitialNowFunction();
-
-volatile NowFunction now_function = InitialNowFunction;
+TimeTicks InitialNowFunction() {
+ InitializeNowFunctionPointers();
+ return g_now_function();
+}
-TimeDelta InitialNowFunction() {
- if (!CPUReliablySupportsHighResTime()) {
- InterlockedExchangePointer(
- reinterpret_cast<void* volatile*>(&now_function),
- &RolloverProtectedNow);
- return RolloverProtectedNow();
- }
- InterlockedExchangePointer(
- reinterpret_cast<void* volatile*>(&now_function),
- &HighResNowWrapper);
- return HighResNowWrapper();
+TimeTicks InitialSystemTraceNowFunction() {
+ InitializeNowFunctionPointers();
+ return g_system_trace_now_function();
}
} // namespace
@@ -491,27 +483,24 @@ TimeDelta InitialNowFunction() {
// static
TimeTicks::TickFunctionType TimeTicks::SetMockTickFunction(
TickFunctionType ticker) {
- base::AutoLock locked(rollover_lock);
- TickFunctionType old = tick_function;
- tick_function = ticker;
- rollover_ms = 0;
- last_seen_now = 0;
+ base::AutoLock locked(g_rollover_lock);
+ TickFunctionType old = g_tick_function;
+ g_tick_function = ticker;
+ g_rollover_ms = 0;
+ g_last_seen_now = 0;
return old;
}
// static
TimeTicks TimeTicks::Now() {
- return TimeTicks() + now_function();
+ return g_now_function();
}
// static
-TimeTicks TimeTicks::HighResNow() {
- return TimeTicks() + HighResNowWrapper();
-}
-
-// static
-bool TimeTicks::IsHighResNowFastAndReliable() {
- return CPUReliablySupportsHighResTime();
+bool TimeTicks::IsHighResolution() {
+ if (g_now_function == &InitialNowFunction)
+ InitializeNowFunctionPointers();
+ return g_now_function == &QPCNow;
}
// static
@@ -522,27 +511,17 @@ TimeTicks TimeTicks::ThreadNow() {
// static
TimeTicks TimeTicks::NowFromSystemTraceTime() {
- return HighResNow();
-}
-
-// static
-int64 TimeTicks::GetQPCDriftMicroseconds() {
- return GetHighResNowSingleton()->GetQPCDriftMicroseconds();
+ return g_system_trace_now_function();
}
// static
TimeTicks TimeTicks::FromQPCValue(LONGLONG qpc_value) {
- return TimeTicks(GetHighResNowSingleton()->QPCValueToMicroseconds(qpc_value));
-}
-
-// static
-bool TimeTicks::IsHighResClockWorking() {
- return GetHighResNowSingleton()->IsUsingHighResClock();
+ return TimeTicks() + QPCValueToTimeDelta(qpc_value);
}
// TimeDelta ------------------------------------------------------------------
// static
TimeDelta TimeDelta::FromQPCValue(LONGLONG qpc_value) {
- return TimeDelta(GetHighResNowSingleton()->QPCValueToMicroseconds(qpc_value));
+ return QPCValueToTimeDelta(qpc_value);
}

Powered by Google App Engine
This is Rietveld 408576698