Chromium Code Reviews| Index: base/time/time_win.cc |
| diff --git a/base/time/time_win.cc b/base/time/time_win.cc |
| index 9b4f17d6613c7e60b8576adc9ae3b2a6afc8c8a3..a10d80587fe2955d611fb6e90f745edbe8d19535 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,31 +321,26 @@ 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 |
| @@ -373,117 +368,91 @@ bool IsBuggyAthlon(const base::CPU& cpu) { |
| // (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; |
| - } |
| +using NowFunction = TimeTicks (*)(void); |
| - TimeDelta Now() { |
| - if (IsUsingHighResClock()) |
| - return TimeDelta::FromMicroseconds(UnreliableNow()); |
| - |
| - // Just fallback to the slower clock. |
| - return RolloverProtectedNow(); |
| - } |
| - |
| - int64 GetQPCDriftMicroseconds() { |
| - if (!IsUsingHighResClock()) |
| - return 0; |
| - return abs((UnreliableNow() - ReliableNow()) - skew_); |
| - } |
| +TimeTicks InitialNowFunction(); |
| +TimeTicks InitialSystemTraceNowFunction(); |
| - 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; |
| - } |
| +volatile NowFunction g_now_function = &InitialNowFunction; |
| +volatile NowFunction g_system_trace_now_function = |
| + &InitialSystemTraceNowFunction; |
| +int64 g_qpc_ticks_per_second = 0; |
| - private: |
| - // Get the number of microseconds since boot in an unreliable fashion. |
| - int64 UnreliableNow() { |
| - LARGE_INTEGER now; |
| - QueryPerformanceCounter(&now); |
| - return QPCValueToMicroseconds(now.QuadPart); |
| - } |
| +TimeDelta QPCValueToTimeDelta(LONGLONG qpc_value) { |
| + DCHECK_GT(g_qpc_ticks_per_second, 0); |
| - // Get the number of microseconds since boot in a reliable fashion. |
| - int64 ReliableNow() { |
| - return RolloverProtectedNow().InMicroseconds(); |
| + // 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); |
| } |
| - |
| - 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(); |
| + // 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)); |
| } |
| -TimeDelta HighResNowWrapper() { |
| - return GetHighResNowSingleton()->Now(); |
| +TimeTicks QPCNow() { |
| + LARGE_INTEGER now; |
| + QueryPerformanceCounter(&now); |
| + return TimeTicks() + QPCValueToTimeDelta(now.QuadPart); |
| } |
| -typedef TimeDelta (*NowFunction)(void); |
| - |
| -bool CPUReliablySupportsHighResTime() { |
| +void InitializeNowFunctionPointers() { |
| + LARGE_INTEGER ticks_per_sec = {0}; |
| + if (QueryPerformanceFrequency(&ticks_per_sec)) |
| + g_qpc_ticks_per_second = ticks_per_sec.QuadPart; |
| + else |
| + g_qpc_ticks_per_second = 0; |
| + |
| + // If Windows does not offer a working QPC implementation, both Now() and |
| + // NowFromSystemTraceTime() must use the low-resolution clock. Note that |
| + // Windows may report a working QPC implementation even on certain Athlon X2 |
| + // CPUs (where QPC has been shown to be unreliable). |
| + // |
| + // Otherwise, if the CPU does not have a non-stop time counter, assume Windows |
| + // will provide an alternate QPC implementation that works, but is expensive |
| + // to use. In this case, Now() should use the inexpensive, low-resolution |
| + // clock and NowFromSystemTraceTime() will use the expensive-but-working QPC |
| + // clock. |
| + // |
| + // 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 ((g_qpc_ticks_per_second <= 0) || |
| + (cpu.vendor_name() == "AuthenticAMD" && cpu.family() == 15)) { |
| + now_function = system_trace_now_function = &RolloverProtectedNow; |
| + } else if (!cpu.has_non_stop_time_stamp_counter()) { |
| + now_function = &RolloverProtectedNow; |
| + system_trace_now_function = &QPCNow; |
| + } else { |
| + now_function = system_trace_now_function = &QPCNow; |
| + } |
| - return true; |
| + InterlockedExchangePointer( |
|
brucedawson
2015/01/07 01:49:26
What protection is InterlockedExchangePointer supp
brianderson
2015/01/07 01:53:07
Pointers will be 32-bits on 32-bit systems, so sim
brianderson
2015/01/07 01:54:53
If we do get rid of the InterlockedExchanges, we'd
miu
2015/01/07 05:22:45
Done.
miu
2015/01/07 05:22:45
Yep. Eliminated in PS3.
miu
2015/01/07 05:22:45
There was a subtlety in the old code. Use of the
|
| + reinterpret_cast<void* volatile*>(&g_now_function), |
| + now_function); |
| + InterlockedExchangePointer( |
| + reinterpret_cast<void* volatile*>(&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 +460,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 +488,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); |
| } |