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..a6109d1f1d5f82e887189b5213f09464f1a63766 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,160 @@ 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: |
|
brucedawson
2015/01/08 19:37:48
To be clear, this is what atomic_thread_fence does
miu
2015/01/08 19:45:56
Done.
|
| +#define atomic_thread_fence(memory_order) _ReadWriteBarrier(); |
|
jamesr
2015/01/08 20:31:54
this doesn't follow macro naming rules: http://goo
miu
2015/01/08 22:16:00
Done. Fixed capitalization.
|
| + |
| +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 +482,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 +510,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); |
| } |