Bug 784859 - Part 6: Remove the need for locking in all calls to TimeStamp::Now; r=bbondy
authorEhsan Akhgari <ehsan@mozilla.com>
Sat, 08 Sep 2012 14:13:17 -0400
changeset 104653 d4a8c7efef04
parent 104652 9544f94ccdb8
child 104654 898df3138008
push id23436
push userryanvm@gmail.com
push date2012-09-09 01:10 +0000
treeherdermozilla-central@f31d1aa89848 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbondy
bugs784859
milestone18.0a1
Bug 784859 - Part 6: Remove the need for locking in all calls to TimeStamp::Now; r=bbondy
xpcom/ds/TimeStamp_windows.cpp
--- a/xpcom/ds/TimeStamp_windows.cpp
+++ b/xpcom/ds/TimeStamp_windows.cpp
@@ -170,17 +170,17 @@ static LONGLONG sOverrunThreshold;
 static const DWORD kLockSpinCount = 4096;
 
 // Common mutex (thanks the relative complexity of the logic, this is better
 // then using CMPXCHG8B.)
 // It is protecting the globals bellow.
 CRITICAL_SECTION sTimeStampLock;
 
 // ----------------------------------------------------------------------------
-// Globals heavily chaning at runtime, protected with sTimeStampLock mutex
+// Globals heavily changing at runtime, protected with sTimeStampLock mutex
 // ----------------------------------------------------------------------------
 
 // The calibrated difference between QPC and GTC.
 //
 // Kept in [mt]
 static LONGLONG sSkew = 0;
 
 // Keeps the last result we have returned from sGetTickCount64 (bellow).  Protects
@@ -195,23 +195,32 @@ static ULONGLONG sLastGTCResult = 0;
 // Kept in [mt]
 static ULONGLONG sLastResult = 0;
 
 // Time of the last performed calibration.
 //
 // Kept in [ms]
 static ULONGLONG sLastCalibrated;
 
+// The following variable stores two booleans, both initialized to false.
+//
+// The lower word is fallbackToGTC:
 // After we have detected a run out of bounderies set this to true.  This
 // then disallows use of QPC result for the hi-res timer.
-static bool sFallBackToGTC = false;
-
+//
+// The higher word is forceRecalibrate:
 // Set to true to force recalibration on QPC read.  This is generally set after
 // system wake up, during which skew can change a lot.
-static bool sForceRecalibrate = false;
+static union CalibrationFlags {
+  struct {
+    bool fallBackToGTC;
+    bool forceRecalibrate;
+  } flags;
+  uint32_t dwordValue;
+} sCalibrationFlags;
 
 
 namespace mozilla {
 
 
 static ULONGLONG
 CalibratedPerformanceCounter();
 
@@ -308,18 +317,21 @@ NS_IMETHODIMP
 StandbyObserver::Observe(nsISupports *subject,
                          const char *topic,
                          const PRUnichar *data)
 {
   AutoCriticalSection lock(&sTimeStampLock);
 
   // Clear the potentiall fallback flag now and try using
   // QPC again after wake up.
-  sFallBackToGTC = false;
-  sForceRecalibrate = true;
+  CalibrationFlags value;
+  value.flags.fallBackToGTC = false;
+  value.flags.forceRecalibrate = true;
+  sCalibrationFlags.dwordValue = value.dwordValue; // aligned 32-bit writes are atomic
+
   LOG(("TimeStamp: system has woken up, reset GTC fallback"));
 
   return NS_OK;
 }
 
 
 // ----------------------------------------------------------------------------
 // The timer core implementation
@@ -443,17 +455,17 @@ PerformanceCounter()
   ::QueryPerformanceCounter(&pc);
   return pc.QuadPart * 1000ULL;
 }
 
 // Called when we detect a larger deviation of QPC to disable it.
 static inline void
 RecordFlaw()
 {
-  sFallBackToGTC = true;
+  sCalibrationFlags.flags.fallBackToGTC = true;
 
   LOG(("TimeStamp: falling back to GTC :("));
 
 #if 0
   // This code has been disabled, because we:
   // 0. InitResolution must not be called under the lock (would reenter) while
   //    we shouldn't release it here just to allow it
   // 1. may return back to using QPC after system wake up
@@ -476,52 +488,56 @@ RecordFlaw()
 // Arguments:
 // overflow - the calculated overflow out of the bounderies for skew difference
 // qpc - current value of QueryPerformanceCounter
 // gtc - current value of GetTickCount, more actual according possible system
 //       sleep between read of QPC and GTC
 static inline bool
 CheckCalibration(LONGLONG overflow, ULONGLONG qpc, ULONGLONG gtc)
 {
-  if (sFallBackToGTC) {
+  CalibrationFlags value;
+  value.dwordValue = sCalibrationFlags.dwordValue; // aligned 32-bit reads are atomic
+  if (value.flags.fallBackToGTC) {
     // We are forbidden to use QPC
     return false;
   }
 
   ULONGLONG sinceLastCalibration = gtc - sLastCalibrated;
 
-  if (overflow && !sForceRecalibrate) {
+  if (overflow && !value.flags.forceRecalibrate) {
     // Calculate trend of the overflow to correspond to the calibration
     // interval, we may get here long after the last calibration because we
     // either didn't read the hi-res function or the system was suspended.
     ULONGLONG trend = LONGLONG(overflow *
       (double(kCalibrationInterval) / sinceLastCalibration));
 
     LOG(("TimeStamp: calibration after %llus with overflow %1.4fms"
          ", adjusted trend per calibration interval is %1.4fms",
          sinceLastCalibration / 1000,
          mt2ms_d(overflow),
          mt2ms_d(trend)));
 
     if (trend > ms2mt(kOverflowLimit)) {
-      // This sets sFallBackToGTC, we have detected
+      // This sets fallBackToGTC, we have detected
       // an unreliability of QPC, stop using it.
+      AutoCriticalSection lock(&sTimeStampLock);
       RecordFlaw();
       return false;
     }
   }
 
-  if (sinceLastCalibration > kCalibrationInterval || sForceRecalibrate) {
+  if (sinceLastCalibration > kCalibrationInterval || value.flags.forceRecalibrate) {
     // Recalculate the skew now
+    AutoCriticalSection lock(&sTimeStampLock);
     sSkew = qpc - ms2mt(gtc);
     sLastCalibrated = gtc;
     LOG(("TimeStamp: new skew is %1.2fms (force:%d)",
-      mt2ms_d(sSkew), sForceRecalibrate));
+      mt2ms_d(sSkew), value.flags.forceRecalibrate));
 
-    sForceRecalibrate = false;
+    sCalibrationFlags.flags.forceRecalibrate = false;
   }
 
   return true;
 }
 
 // AtomicStoreIfGreaterThan tries to store the maximum of two values in one of them
 // without locking.  The only scenario in which two racing threads may corrupt the
 // maximum value is when they both try to increase the value without knowing about
@@ -565,41 +581,36 @@ CalibratedPerformanceCounter()
   // the largest bottleneck, let threads read the value concurently to have
   // possibly a better performance.
 
   ULONGLONG qpc = PerformanceCounter();
 
   // Rollover protection
   ULONGLONG gtc = sGetTickCount64();
 
-  ULONGLONG result;
-  {
-  AutoCriticalSection lock(&sTimeStampLock);
-
   LONGLONG diff = qpc - ms2mt(gtc) - sSkew;
   LONGLONG overflow = 0;
 
   if (diff < sUnderrunThreshold) {
     overflow = sUnderrunThreshold - diff;
   }
   else if (diff > sOverrunThreshold) {
     overflow = diff - sOverrunThreshold;
   }
 
-  result = qpc;
+  ULONGLONG result = qpc;
   if (!CheckCalibration(overflow, qpc, gtc)) {
     // We are back on GTC, QPC has been observed unreliable
     result = ms2mt(gtc) + sSkew;
   }
 
 #if 0
   LOG(("TimeStamp: result = %1.2fms, diff = %1.4fms",
       mt2ms_d(result), mt2ms_d(diff)));
 #endif
-  }
 
   return AtomicStoreIfGreaterThan(&sLastResult, result);
 }
 
 // ----------------------------------------------------------------------------
 // TimeDuration and TimeStamp implementation
 // ----------------------------------------------------------------------------
 
@@ -666,17 +677,17 @@ TimeStamp::Startup()
 
   InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount);
 
   LARGE_INTEGER freq;
   BOOL QPCAvailable = ::QueryPerformanceFrequency(&freq);
   if (!QPCAvailable) {
     // No Performance Counter.  Fall back to use GetTickCount.
     sFrequencyPerSec = 1;
-    sFallBackToGTC = true;
+    sCalibrationFlags.flags.fallBackToGTC = true;
     InitResolution();
 
     LOG(("TimeStamp: using GetTickCount"));
     return NS_OK;
   }
 
   sFrequencyPerSec = freq.QuadPart;