Bug 721543 - Call forgetSkippable before CC, r=mccr8
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Mon, 30 Jan 2012 22:06:18 +0200
changeset 86925 de4f382f487ba1da03d6233e27465056e4e747ac
parent 86924 f410bdf30132fa51821a089bf5db563952c8c4dc
child 86926 aa0476948dc0bf46db3e702f2cc65979d0a38252
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs721543
milestone12.0a1
Bug 721543 - Call forgetSkippable before CC, r=mccr8
dom/base/nsDOMWindowUtils.cpp
dom/base/nsJSEnvironment.cpp
dom/base/nsJSEnvironment.h
dom/interfaces/base/nsIDOMWindowUtils.idl
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -801,43 +801,45 @@ nsDOMWindowUtils::Focus(nsIDOMElement* a
     else
       fm->ClearFocus(mWindow);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::GarbageCollect(nsICycleCollectorListener *aListener)
+nsDOMWindowUtils::GarbageCollect(nsICycleCollectorListener *aListener,
+                                 PRInt32 aExtraForgetSkippableCalls)
 {
   SAMPLE_LABEL("GC", "GarbageCollect");
   // Always permit this in debug builds.
 #ifndef DEBUG
   if (!IsUniversalXPConnectCapable()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 #endif
 
   nsJSContext::GarbageCollectNow(js::gcreason::DOM_UTILS);
-  nsJSContext::CycleCollectNow(aListener);
+  nsJSContext::CycleCollectNow(aListener, aExtraForgetSkippableCalls);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::CycleCollect(nsICycleCollectorListener *aListener)
+nsDOMWindowUtils::CycleCollect(nsICycleCollectorListener *aListener,
+                               PRInt32 aExtraForgetSkippableCalls)
 {
   // Always permit this in debug builds.
 #ifndef DEBUG
   if (!IsUniversalXPConnectCapable()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 #endif
 
-  nsJSContext::CycleCollectNow(aListener);
+  nsJSContext::CycleCollectNow(aListener, aExtraForgetSkippableCalls);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendSimpleGestureEvent(const nsAString& aType,
                                          float aX,
                                          float aY,
                                          PRUint32 aDirection,
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -134,16 +134,20 @@ static PRLogModuleInfo* gJSDiagnostics;
 // The amount of time we wait from the first request to GC to actually
 // doing the first GC.
 #define NS_FIRST_GC_DELAY           10000 // ms
 
 // The amount of time we wait between a request to CC (after GC ran)
 // and doing the actual CC.
 #define NS_CC_DELAY                 5000 // ms
 
+#define NS_CC_SKIPPABLE_DELAY       250 // ms
+
+#define NS_CC_FORCED                (5 * 60 * PR_USEC_PER_SEC) // 5 min
+
 #define JAVASCRIPT nsIProgrammingLanguage::JAVASCRIPT
 
 // if you add statics here, add them to the list in nsJSRuntime::Startup
 
 static nsITimer *sGCTimer;
 static nsITimer *sShrinkGCBuffersTimer;
 static nsITimer *sCCTimer;
 
@@ -157,16 +161,25 @@ static bool sGCHasRun;
 // all cases. This counter also gets reset if we end up GC'ing while
 // we're waiting for a slow page to load. IOW, this count may be 0
 // even when there are pending loads.
 static PRUint32 sPendingLoadCount;
 static bool sLoadingInProgress;
 
 static PRUint32 sCCollectedWaitingForGC;
 static bool sPostGCEventsToConsole;
+static PRUint32 sCCTimerFireCount = 0;
+static PRUint32 sMinForgetSkippableTime = PR_UINT32_MAX;
+static PRUint32 sMaxForgetSkippableTime = 0;
+static PRUint32 sTotalForgetSkippableTime = 0;
+static PRUint32 sRemovedPurples = 0;
+static PRUint32 sForgetSkippableBeforeCC = 0;
+static PRUint32 sPreviousSuspectedCount = 0;
+
+static bool sCleanupSinceLastGC = true;
 
 nsScriptNameSpaceManager *gNameSpaceManager;
 
 static nsIJSRuntimeService *sRuntimeService;
 JSRuntime *nsJSRuntime::sRuntime;
 
 static const char kJSRuntimeServiceContractID[] =
   "@mozilla.org/js/xpc/RuntimeService;1";
@@ -3251,30 +3264,40 @@ nsJSContext::ShrinkGCBuffersNow()
 
   KillShrinkGCBuffersTimer();
 
   JS_ShrinkGCBuffers(nsJSRuntime::sRuntime);
 }
 
 //Static
 void
-nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener)
+nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener,
+                             PRInt32 aExtraForgetSkippableCalls)
 {
   if (!NS_IsMainThread()) {
     return;
   }
 
   SAMPLE_LABEL("GC", "CycleCollectNow");
   NS_TIME_FUNCTION_MIN(1.0);
 
   KillCCTimer();
 
   PRTime start = PR_Now();
 
   PRUint32 suspected = nsCycleCollector_suspectedCount();
+
+  for (PRInt32 i = 0; i < aExtraForgetSkippableCalls; ++i) {
+    nsCycleCollector_forgetSkippable();
+  }
+
+  // nsCycleCollector_forgetSkippable may mark some gray js to black.
+  if (!sCleanupSinceLastGC && aExtraForgetSkippableCalls >= 0) {
+    nsCycleCollector_forgetSkippable();
+  }
   PRUint32 collected = nsCycleCollector_collect(aListener);
   sCCollectedWaitingForGC += collected;
 
   // If we collected a substantial amount of cycles, poke the GC since more objects
   // might be unreachable now.
   if (sCCollectedWaitingForGC > 250) {
     PokeGC(js::gcreason::CC_WAITING);
   }
@@ -3290,28 +3313,45 @@ nsJSContext::CycleCollectNow(nsICycleCol
   if (sPostGCEventsToConsole) {
     PRTime delta = 0;
     if (sFirstCollectionTime) {
       delta = now - sFirstCollectionTime;
     } else {
       sFirstCollectionTime = now;
     }
 
-    NS_NAMED_LITERAL_STRING(kFmt,
-                            "CC(T+%.1f) collected: %lu (%lu waiting for GC), suspected: %lu, duration: %llu ms.");
+    NS_NAMED_MULTILINE_LITERAL_STRING(kFmt,
+      NS_LL("CC(T+%.1f) collected: %lu (%lu waiting for GC), suspected: %lu, duration: %llu ms.\n")
+      NS_LL("ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, removed: %lu"));
     nsString msg;
+    PRUint32 cleanups = sForgetSkippableBeforeCC ? sForgetSkippableBeforeCC : 1;
+    sMinForgetSkippableTime = (sMinForgetSkippableTime == PR_UINT32_MAX)
+      ? 0 : sMinForgetSkippableTime;
     msg.Adopt(nsTextFormatter::smprintf(kFmt.get(), double(delta) / PR_USEC_PER_SEC,
                                         collected, sCCollectedWaitingForGC, suspected,
-                                        (now - start) / PR_USEC_PER_MSEC));
+                                        (now - start) / PR_USEC_PER_MSEC,
+                                        sForgetSkippableBeforeCC,
+                                        sMinForgetSkippableTime / PR_USEC_PER_MSEC,
+                                        sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
+                                        (sTotalForgetSkippableTime / cleanups) /
+                                          PR_USEC_PER_MSEC,
+                                        sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
+                                        sRemovedPurples));
     nsCOMPtr<nsIConsoleService> cs =
       do_GetService(NS_CONSOLESERVICE_CONTRACTID);
     if (cs) {
       cs->LogStringMessage(msg.get());
     }
   }
+  sMinForgetSkippableTime = PR_UINT32_MAX;
+  sMaxForgetSkippableTime = 0;
+  sTotalForgetSkippableTime = 0;
+  sRemovedPurples = 0;
+  sForgetSkippableBeforeCC = 0;
+  sCleanupSinceLastGC = true;
 }
 
 // static
 void
 GCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   NS_RELEASE(sGCTimer);
 
@@ -3326,19 +3366,60 @@ ShrinkGCBuffersTimerFired(nsITimer *aTim
 
   nsJSContext::ShrinkGCBuffersNow();
 }
 
 // static
 void
 CCTimerFired(nsITimer *aTimer, void *aClosure)
 {
-  NS_RELEASE(sCCTimer);
-
-  nsJSContext::CycleCollectNow();
+  if (sDidShutdown) {
+    return;
+  }
+  ++sCCTimerFireCount;
+  if (sCCTimerFireCount < (NS_CC_DELAY / NS_CC_SKIPPABLE_DELAY)) {
+    PRUint32 suspected = nsCycleCollector_suspectedCount();
+    if ((sPreviousSuspectedCount + 100) > suspected) {
+      // Just few new suspected objects, return early.
+      return;
+    }
+    sPreviousSuspectedCount = suspected;
+    PRTime startTime;
+    if (sPostGCEventsToConsole) {
+      startTime = PR_Now();
+    }
+    nsCycleCollector_forgetSkippable();
+    sCleanupSinceLastGC = true;
+    if (sPostGCEventsToConsole) {
+      PRTime delta = PR_Now() - startTime;
+      if (sMinForgetSkippableTime > delta) {
+        sMinForgetSkippableTime = delta;
+      }
+      if (sMaxForgetSkippableTime < delta) {
+        sMaxForgetSkippableTime = delta;
+      }
+      sTotalForgetSkippableTime += delta;
+      sRemovedPurples += (suspected - nsCycleCollector_suspectedCount());
+      ++sForgetSkippableBeforeCC;
+    }
+  } else {
+    sPreviousSuspectedCount = 0;
+    nsJSContext::KillCCTimer();
+    if (nsCycleCollector_suspectedCount() > 500 ||
+        sLastCCEndTime + NS_CC_FORCED < PR_Now()) {
+      nsJSContext::CycleCollectNow();
+    }
+  }
+}
+
+// static
+bool
+nsJSContext::CleanupSinceLastGC()
+{
+  return sCleanupSinceLastGC;
 }
 
 // static
 void
 nsJSContext::LoadStart()
 {
   sLoadingInProgress = true;
   ++sPendingLoadCount;
@@ -3409,40 +3490,31 @@ nsJSContext::PokeShrinkGCBuffers()
                                               NS_SHRINK_GC_BUFFERS_DELAY,
                                               nsITimer::TYPE_ONE_SHOT);
 }
 
 // static
 void
 nsJSContext::MaybePokeCC()
 {
-  if (nsCycleCollector_suspectedCount() > 1000) {
-    PokeCC();
-  }
-}
-
-// static
-void
-nsJSContext::PokeCC()
-{
-  if (sCCTimer || !sGCHasRun) {
-    // There's already a timer for GC'ing, or GC hasn't run yet, just return.
+  if (sCCTimer) {
     return;
   }
 
-  CallCreateInstance("@mozilla.org/timer;1", &sCCTimer);
-
-  if (!sCCTimer) {
-    // Failed to create timer (probably because we're in XPCOM shutdown)
-    return;
+  if (nsCycleCollector_suspectedCount() > 100 ||
+      sLastCCEndTime + NS_CC_FORCED < PR_Now()) {
+    sCCTimerFireCount = 0;
+    CallCreateInstance("@mozilla.org/timer;1", &sCCTimer);
+    if (!sCCTimer) {
+      return;
+    }
+    sCCTimer->InitWithFuncCallback(CCTimerFired, nsnull,
+                                   NS_CC_SKIPPABLE_DELAY,
+                                   nsITimer::TYPE_REPEATING_SLACK);
   }
-
-  sCCTimer->InitWithFuncCallback(CCTimerFired, nsnull,
-                                 NS_CC_DELAY,
-                                 nsITimer::TYPE_ONE_SHOT);
 }
 
 //static
 void
 nsJSContext::KillGCTimer()
 {
   if (sGCTimer) {
     sGCTimer->Cancel();
@@ -3499,16 +3571,18 @@ DOMGCFinishedCallback(JSRuntime *rt, JSC
                                         double(delta) / PR_USEC_PER_SEC, status));
     nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
     if (cs) {
       cs->LogStringMessage(msg.get());
     }
   }
 
   sCCollectedWaitingForGC = 0;
+  sCleanupSinceLastGC = false;
+
   if (sGCTimer) {
     // If we were waiting for a GC to happen, kill the timer.
     nsJSContext::KillGCTimer();
 
     // If this is a compartment GC, restart it. We still want
     // a full GC to happen. Compartment GCs usually happen as a
     // result of last-ditch or MaybeGC. In both cases its
     // probably a time of heavy activity and we want to delay
@@ -3518,17 +3592,17 @@ DOMGCFinishedCallback(JSRuntime *rt, JSC
 
       // We poked the GC, so we can kill any pending CC here.
       nsJSContext::KillCCTimer();
     }
   } else {
     // If this was a full GC, poke the CC to run soon.
     if (!comp) {
       sGCHasRun = true;
-      nsJSContext::PokeCC();
+      nsJSContext::MaybePokeCC();
     }
   }
 
   // If we didn't end up scheduling a GC, make sure that we release GC buffers
   // soon after canceling previous shrinking attempt 
   nsJSContext::KillShrinkGCBuffersTimer();
   if (!sGCTimer) {
     nsJSContext::PokeShrinkGCBuffers();
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -178,30 +178,34 @@ public:
 
   NS_DECL_NSIXPCSCRIPTNOTIFY
 
   static void LoadStart();
   static void LoadEnd();
 
   static void GarbageCollectNow(js::gcreason::Reason reason, PRUint32 gckind = nsGCNormal);
   static void ShrinkGCBuffersNow();
-  static void CycleCollectNow(nsICycleCollectorListener *aListener = nsnull);
+  // If aExtraForgetSkippableCalls is -1, forgetSkippable won't be
+  // called even if the previous collection was GC.
+  static void CycleCollectNow(nsICycleCollectorListener *aListener = nsnull,
+                              PRInt32 aExtraForgetSkippableCalls = 0);
 
   static void PokeGC(js::gcreason::Reason aReason);
   static void KillGCTimer();
 
   static void PokeShrinkGCBuffers();
   static void KillShrinkGCBuffersTimer();
 
-  static void PokeCC();
   static void MaybePokeCC();
   static void KillCCTimer();
 
   virtual void GC(js::gcreason::Reason aReason);
 
+  static bool CleanupSinceLastGC();
+
   nsIScriptGlobalObject* GetCachedGlobalObject()
   {
     // Verify that we have a global so that this
     // does always return a null when GetGlobalObject() is null.
     JSObject* global = JS_GetGlobalObject(mContext);
     return global ? mGlobalObjectRef.get() : nsnull;
   }
 protected:
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -65,17 +65,17 @@ interface nsIDOMEvent;
 interface nsITransferable;
 interface nsIQueryContentEventResult;
 interface nsIDOMWindow;
 interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 
-[scriptable, uuid(e01171b0-712a-47ce-8552-b7b2ef0a2507)]
+[scriptable, uuid(ab6e9c71-8aa1-40bb-8bf9-65e16429055f)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -398,30 +398,44 @@ interface nsIDOMWindowUtils : nsISupport
    * Force a garbage collection followed by a cycle collection.
    *
    * Will throw a DOM security error if called without UniversalXPConnect
    * privileges in non-debug builds. Available to all callers in debug builds.
    *
    * @param aListener listener that receives information about the CC graph
    *                  (see @mozilla.org/cycle-collector-logger;1 for a logger
    *                   component)
+   * @param aExtraForgetSkippableCalls indicates how many times
+   *                                   nsCycleCollector_forgetSkippable will
+   *                                   be called before running cycle collection.
+   *                                   -1 prevents the default
+   *                                   nsCycleCollector_forgetSkippable call
+   *                                   which happens after garbage collection.
    */
-  void garbageCollect([optional] in nsICycleCollectorListener aListener);
+  void garbageCollect([optional] in nsICycleCollectorListener aListener,
+                      [optional] in long aExtraForgetSkippableCalls);
 
   /**
    * Force a cycle collection without garbage collection.
    *
    * Will throw a DOM security error if called without UniversalXPConnect
    * privileges in non-debug builds. Available to all callers in debug builds.
    *
    * @param aListener listener that receives information about the CC graph
    *                  (see @mozilla.org/cycle-collector-logger;1 for a logger
    *                   component)
+   * @param aExtraForgetSkippableCalls indicates how many times
+   *                                   nsCycleCollector_forgetSkippable will
+   *                                   be called before running cycle collection.
+   *                                   -1 prevents the default
+   *                                   nsCycleCollector_forgetSkippable call
+   *                                   which happens after garbage collection.
    */
-  void cycleCollect([optional] in nsICycleCollectorListener aListener);
+  void cycleCollect([optional] in nsICycleCollectorListener aListener,
+                    [optional] in long aExtraForgetSkippableCalls);
 
   /** Synthesize a simple gesture event for a window. The event types
    *  supported are: MozSwipeGesture, MozMagnifyGestureStart,
    *  MozMagnifyGestureUpdate, MozMagnifyGesture, MozRotateGestureStart,
    *  MozRotateGestureUpdate, MozRotateGesture, MozPressTapGesture, and
    *  MozTapGesture.
    *
    * Cannot be accessed from unprivileged context (not