From 81e4550796e89d6c36e6cc811300018643f84c67 Mon Sep 17 00:00:00 2001 From: "bmeurer@chromium.org" Date: Wed, 2 Oct 2013 13:30:31 +0000 Subject: [PATCH] Always use timeGetTime() for TimeTicks::Now() on Windows. This way, we also ensure that timeGetTime() is used for Time::Now(), and thereby Date.now() even if GetTickCount64() is available. Also add test coverage for Time::Now(), TimeTicks::Now() and TimeTicks::HighResNow(). BUG=chromium:288924 TEST=cctest/test-timer R=hpayer@chromium.org Review URL: https://codereview.chromium.org/25468003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17080 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/platform-win32.cc | 4 +- src/platform/elapsed-timer.h | 2 +- src/platform/time.cc | 134 +++++++++++++++-------------------- src/platform/time.h | 5 +- src/utils/random-number-generator.cc | 2 +- test/cctest/test-time.cc | 53 +++++++++++++- test/mjsunit/mjsunit.status | 4 -- test/mjsunit/timer.js | 53 -------------- 8 files changed, 115 insertions(+), 142 deletions(-) delete mode 100644 test/mjsunit/timer.js diff --git a/src/platform-win32.cc b/src/platform-win32.cc index 3283dfa..35411bf 100644 --- a/src/platform-win32.cc +++ b/src/platform-win32.cc @@ -595,9 +595,7 @@ int OS::GetUserTime(uint32_t* secs, uint32_t* usecs) { // Returns current time as the number of milliseconds since // 00:00:00 UTC, January 1, 1970. double OS::TimeCurrentMillis() { - Win32Time t; - t.SetToCurrentTime(); - return t.ToJSTime(); + return Time::Now().ToJsTime(); } diff --git a/src/platform/elapsed-timer.h b/src/platform/elapsed-timer.h index 2311db2..9016a22 100644 --- a/src/platform/elapsed-timer.h +++ b/src/platform/elapsed-timer.h @@ -104,7 +104,7 @@ class ElapsedTimer V8_FINAL BASE_EMBEDDED { private: static V8_INLINE TimeTicks Now() { - TimeTicks now = TimeTicks::HighResNow(); + TimeTicks now = TimeTicks::HighResolutionNow(); ASSERT(!now.IsNull()); return now; } diff --git a/src/platform/time.cc b/src/platform/time.cc index ea6dd2c..de0ca16 100644 --- a/src/platform/time.cc +++ b/src/platform/time.cc @@ -43,13 +43,6 @@ #include "win32-headers.h" #endif -#if V8_OS_WIN -// Prototype for GetTickCount64() procedure. -extern "C" { -typedef ULONGLONG (WINAPI *GETTICKCOUNT64PROC)(void); -} -#endif - namespace v8 { namespace internal { @@ -175,43 +168,43 @@ struct timespec TimeDelta::ToTimespec() const { // periodically resync the internal clock to the system clock. class Clock V8_FINAL { public: - Clock() : initial_time_(CurrentWallclockTime()), - initial_ticks_(TimeTicks::Now()) {} + Clock() : initial_ticks_(GetSystemTicks()), initial_time_(GetSystemTime()) {} Time Now() { - // This must be executed under lock. - LockGuard lock_guard(&mutex_); + // Time between resampling the un-granular clock for this API (1 minute). + const TimeDelta kMaxElapsedTime = TimeDelta::FromMinutes(1); - // Calculate the time elapsed since we started our timer. - TimeDelta elapsed = TimeTicks::Now() - initial_ticks_; + LockGuard lock_guard(&mutex_); - // Check if we don't need to synchronize with the wallclock yet. - if (elapsed.InMicroseconds() <= kMaxMicrosecondsToAvoidDrift) { - return initial_time_ + elapsed; + // Determine current time and ticks. + TimeTicks ticks = GetSystemTicks(); + Time time = GetSystemTime(); + + // Check if we need to synchronize with the system clock due to a backwards + // time change or the amount of time elapsed. + TimeDelta elapsed = ticks - initial_ticks_; + if (time < initial_time_ || elapsed > kMaxElapsedTime) { + initial_ticks_ = ticks; + initial_time_ = time; + return time; } - // Resynchronize with the wallclock. - initial_ticks_ = TimeTicks::Now(); - initial_time_ = CurrentWallclockTime(); - return initial_time_; + return initial_time_ + elapsed; } Time NowFromSystemTime() { - // This must be executed under lock. LockGuard lock_guard(&mutex_); - - // Resynchronize with the wallclock. - initial_ticks_ = TimeTicks::Now(); - initial_time_ = CurrentWallclockTime(); + initial_ticks_ = GetSystemTicks(); + initial_time_ = GetSystemTime(); return initial_time_; } private: - // Time between resampling the un-granular clock for this API (1 minute). - static const int64_t kMaxMicrosecondsToAvoidDrift = - Time::kMicrosecondsPerMinute; + static TimeTicks GetSystemTicks() { + return TimeTicks::Now(); + } - static Time CurrentWallclockTime() { + static Time GetSystemTime() { FILETIME ft; ::GetSystemTimeAsFileTime(&ft); return Time::FromFiletime(ft); @@ -223,9 +216,9 @@ class Clock V8_FINAL { }; -static LazyDynamicInstance, - ThreadSafeInitOnceTrait>::type clock = LAZY_DYNAMIC_INSTANCE_INITIALIZER; +static LazyStaticInstance, + ThreadSafeInitOnceTrait>::type clock = LAZY_STATIC_INSTANCE_INITIALIZER; Time Time::Now() { @@ -388,6 +381,7 @@ class TickClock { public: virtual ~TickClock() {} virtual int64_t Now() = 0; + virtual bool IsHighResolution() = 0; }; @@ -440,42 +434,24 @@ class HighResolutionTickClock V8_FINAL : public TickClock { int64_t ticks = (whole_seconds * Time::kMicrosecondsPerSecond) + ((leftover_ticks * Time::kMicrosecondsPerSecond) / ticks_per_second_); - // Make sure we never return 0 here, so that TimeTicks::HighResNow() + // Make sure we never return 0 here, so that TimeTicks::HighResolutionNow() // will never return 0. return ticks + 1; } - private: - int64_t ticks_per_second_; -}; - - -// The GetTickCount64() API is what we actually want for the regular tick -// clock, but this is only available starting with Windows Vista. -class WindowsVistaTickClock V8_FINAL : public TickClock { - public: - explicit WindowsVistaTickClock(GETTICKCOUNT64PROC func) : func_(func) { - ASSERT(func_ != NULL); - } - virtual ~WindowsVistaTickClock() {} - - virtual int64_t Now() V8_OVERRIDE { - // Query the current ticks (in ms). - ULONGLONG tick_count_ms = (*func_)(); - - // Convert to microseconds (make sure to never return 0 here). - return (tick_count_ms * Time::kMicrosecondsPerMillisecond) + 1; + virtual bool IsHighResolution() V8_OVERRIDE { + return true; } private: - GETTICKCOUNT64PROC func_; + int64_t ticks_per_second_; }; class RolloverProtectedTickClock V8_FINAL : public TickClock { public: // We initialize rollover_ms_ to 1 to ensure that we will never - // return 0 from TimeTicks::HighResNow() and TimeTicks::Now() below. + // return 0 from TimeTicks::HighResolutionNow() and TimeTicks::Now() below. RolloverProtectedTickClock() : last_seen_now_(0), rollover_ms_(1) {} virtual ~RolloverProtectedTickClock() {} @@ -487,6 +463,9 @@ class RolloverProtectedTickClock V8_FINAL : public TickClock { // Note that we do not use GetTickCount() here, since timeGetTime() gives // more predictable delta values, as described here: // http://blogs.msdn.com/b/larryosterman/archive/2009/09/02/what-s-the-difference-between-gettickcount-and-timegettime.aspx + // timeGetTime() provides 1ms granularity when combined with + // timeBeginPeriod(). If the host application for V8 wants fast timers, it + // can use timeBeginPeriod() to increase the resolution. DWORD now = timeGetTime(); if (now < last_seen_now_) { rollover_ms_ += V8_INT64_C(0x100000000); // ~49.7 days. @@ -495,6 +474,10 @@ class RolloverProtectedTickClock V8_FINAL : public TickClock { return (now + rollover_ms_) * Time::kMicrosecondsPerMillisecond; } + virtual bool IsHighResolution() V8_OVERRIDE { + return false; + } + private: Mutex mutex_; DWORD last_seen_now_; @@ -502,27 +485,10 @@ class RolloverProtectedTickClock V8_FINAL : public TickClock { }; -struct CreateTickClockTrait { - static TickClock* Create() { - // Try to load GetTickCount64() from kernel32.dll (available since Vista). - HMODULE kernel32 = ::GetModuleHandleA("kernel32.dll"); - ASSERT(kernel32 != NULL); - FARPROC proc = ::GetProcAddress(kernel32, "GetTickCount64"); - if (proc != NULL) { - return new WindowsVistaTickClock( - reinterpret_cast(proc)); - } - - // Fallback to the rollover protected tick clock. - return new RolloverProtectedTickClock; - } -}; - - -static LazyDynamicInstance, ThreadSafeInitOnceTrait>::type tick_clock = - LAZY_DYNAMIC_INSTANCE_INITIALIZER; + LAZY_STATIC_INSTANCE_INITIALIZER; struct CreateHighResTickClockTrait { @@ -560,21 +526,27 @@ TimeTicks TimeTicks::Now() { } -TimeTicks TimeTicks::HighResNow() { +TimeTicks TimeTicks::HighResolutionNow() { // Make sure we never return 0 here. TimeTicks ticks(high_res_tick_clock.Pointer()->Now()); ASSERT(!ticks.IsNull()); return ticks; } + +// static +bool TimeTicks::IsHighResolutionClockWorking() { + return high_res_tick_clock.Pointer()->IsHighResolution(); +} + #else // V8_OS_WIN TimeTicks TimeTicks::Now() { - return HighResNow(); + return HighResolutionNow(); } -TimeTicks TimeTicks::HighResNow() { +TimeTicks TimeTicks::HighResolutionNow() { int64_t ticks; #if V8_OS_MACOSX static struct mach_timebase_info info; @@ -608,6 +580,12 @@ TimeTicks TimeTicks::HighResNow() { return TimeTicks(ticks + 1); } + +// static +bool TimeTicks::IsHighResolutionClockWorking() { + return true; +} + #endif // V8_OS_WIN } } // namespace v8::internal diff --git a/src/platform/time.h b/src/platform/time.h index 2ce6cdd..25161f8 100644 --- a/src/platform/time.h +++ b/src/platform/time.h @@ -333,7 +333,10 @@ class TimeTicks V8_FINAL BASE_EMBEDDED { // resolution. THIS CALL IS GENERALLY MUCH MORE EXPENSIVE THAN Now() AND // SHOULD ONLY BE USED WHEN IT IS REALLY NEEDED. // This method never returns a null TimeTicks. - static TimeTicks HighResNow(); + static TimeTicks HighResolutionNow(); + + // Returns true if the high-resolution clock is working on this system. + static bool IsHighResolutionClockWorking(); // Returns true if this object has not been initialized. bool IsNull() const { return ticks_ == 0; } diff --git a/src/utils/random-number-generator.cc b/src/utils/random-number-generator.cc index 3f657fb..fe27331 100644 --- a/src/utils/random-number-generator.cc +++ b/src/utils/random-number-generator.cc @@ -99,7 +99,7 @@ RandomNumberGenerator::RandomNumberGenerator() { // which provides reasonable entropy, see: // https://code.google.com/p/v8/issues/detail?id=2905 int64_t seed = Time::NowFromSystemTime().ToInternalValue() << 24; - seed ^= TimeTicks::HighResNow().ToInternalValue() << 16; + seed ^= TimeTicks::HighResolutionNow().ToInternalValue() << 16; seed ^= TimeTicks::Now().ToInternalValue() << 8; SetSeed(seed); #endif // V8_OS_CYGWIN || V8_OS_WIN diff --git a/test/cctest/test-time.cc b/test/cctest/test-time.cc index 8b92d8d..28d647a 100644 --- a/test/cctest/test-time.cc +++ b/test/cctest/test-time.cc @@ -133,7 +133,7 @@ TEST(TimeTicksIsMonotonic) { timer.Start(); while (!timer.HasExpired(TimeDelta::FromMilliseconds(100))) { TimeTicks normal_ticks = TimeTicks::Now(); - TimeTicks highres_ticks = TimeTicks::HighResNow(); + TimeTicks highres_ticks = TimeTicks::HighResolutionNow(); CHECK_GE(normal_ticks, previous_normal_ticks); CHECK_GE((normal_ticks - previous_normal_ticks).InMicroseconds(), 0); CHECK_GE(highres_ticks, previous_highres_ticks); @@ -142,3 +142,54 @@ TEST(TimeTicksIsMonotonic) { previous_highres_ticks = highres_ticks; } } + + +template +static void ResolutionTest(T (*Now)(), TimeDelta target_granularity) { + // We're trying to measure that intervals increment in a VERY small amount + // of time -- according to the specified target granularity. Unfortunately, + // if we happen to have a context switch in the middle of our test, the + // context switch could easily exceed our limit. So, we iterate on this + // several times. As long as we're able to detect the fine-granularity + // timers at least once, then the test has succeeded. + static const TimeDelta kExpirationTimeout = TimeDelta::FromSeconds(1); + ElapsedTimer timer; + timer.Start(); + TimeDelta delta; + do { + T start = Now(); + T now = start; + // Loop until we can detect that the clock has changed. Non-HighRes timers + // will increment in chunks, i.e. 15ms. By spinning until we see a clock + // change, we detect the minimum time between measurements. + do { + now = Now(); + delta = now - start; + } while (now <= start); + CHECK_NE(static_cast(0), delta.InMicroseconds()); + } while (delta > target_granularity && !timer.HasExpired(kExpirationTimeout)); + CHECK_LE(delta, target_granularity); +} + + +TEST(TimeNowResolution) { + // We assume that Time::Now() has at least 16ms resolution. + static const TimeDelta kTargetGranularity = TimeDelta::FromMilliseconds(16); + ResolutionTest