Bug 897433 - Telemetry for SnowWhite and more async SnowWhite freeing (patch v4), r=mccr8
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Sat, 27 Jul 2013 13:48:45 +0300
changeset 140205 a2c4d9491491
parent 140204 6d730e476cf4
child 140240 75e2498668df
child 155603 334ce2d21650
push id25019
push useropettay@mozilla.com
push dateSat, 27 Jul 2013 11:15:52 +0000
treeherdermozilla-central@a2c4d9491491 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs897433
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 897433 - Telemetry for SnowWhite and more async SnowWhite freeing (patch v4), r=mccr8
dom/base/nsJSEnvironment.cpp
toolkit/components/telemetry/Histograms.json
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsCycleCollector.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -130,16 +130,18 @@ static PRLogModuleInfo* gJSDiagnostics;
 // Trigger a CC if the purple buffer exceeds this size when we check it.
 #define NS_CC_PURPLE_LIMIT          200
 
 #define JAVASCRIPT nsIProgrammingLanguage::JAVASCRIPT
 
 // Large value used to specify that a script should run essentially forever
 #define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32)
 
+#define NS_MAJOR_FORGET_SKIPPABLE_CALLS 2
+
 // if you add statics here, add them to the list in nsJSRuntime::Startup
 
 static nsITimer *sGCTimer;
 static nsITimer *sShrinkGCBuffersTimer;
 static nsITimer *sCCTimer;
 static nsITimer *sFullGCTimer;
 static nsITimer *sInterSliceGCTimer;
 
@@ -2497,17 +2499,19 @@ FinishAnyIncrementalGC()
   }
 }
 
 static void
 FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless)
 {
   PRTime startTime = PR_Now();
   FinishAnyIncrementalGC();
-  nsCycleCollector_forgetSkippable(aRemoveChildless);
+  bool earlyForgetSkippable =
+    sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS;
+  nsCycleCollector_forgetSkippable(aRemoveChildless, earlyForgetSkippable);
   sPreviousSuspectedCount = nsCycleCollector_suspectedCount();
   ++sCleanupsSinceLastGC;
   PRTime delta = PR_Now() - startTime;
   if (sMinForgetSkippableTime > delta) {
     sMinForgetSkippableTime = delta;
   }
   if (sMaxForgetSkippableTime < delta) {
     sMaxForgetSkippableTime = delta;
@@ -2547,18 +2551,19 @@ nsJSContext::CycleCollectNow(nsICycleCol
 
   KillCCTimer();
 
   uint32_t suspected = nsCycleCollector_suspectedCount();
   bool ranSyncForgetSkippable = false;
 
   // Run forgetSkippable synchronously to reduce the size of the CC graph. This
   // is particularly useful if we recently finished a GC.
-  if (sCleanupsSinceLastGC < 2 && aExtraForgetSkippableCalls >= 0) {
-    while (sCleanupsSinceLastGC < 2) {
+  if (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS &&
+      aExtraForgetSkippableCalls >= 0) {
+    while (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS) {
       FireForgetSkippable(nsCycleCollector_suspectedCount(), false);
       ranSyncForgetSkippable = true;
     }
   }
 
   for (int32_t i = 0; i < aExtraForgetSkippableCalls; ++i) {
     FireForgetSkippable(nsCycleCollector_suspectedCount(), false);
     ranSyncForgetSkippable = true;
@@ -3024,19 +3029,16 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G
     sCCLockedOut = true;
     nsJSContext::KillShrinkGCBuffersTimer();
   } else if (aProgress == JS::GC_CYCLE_END) {
     sCCLockedOut = false;
   }
 
   // The GC has more work to do, so schedule another GC slice.
   if (aProgress == JS::GC_SLICE_END) {
-    if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
-      nsCycleCollector_dispatchDeferredDeletion();
-    }
     nsJSContext::KillInterSliceGCTimer();
     if (!sShuttingDown) {
       CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer);
       sInterSliceGCTimer->InitWithFuncCallback(InterSliceGCTimerFired,
                                                NULL,
                                                NS_INTERSLICE_GC_DELAY,
                                                nsITimer::TYPE_ONE_SHOT);
     }
@@ -3068,16 +3070,21 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G
       nsJSContext::KillFullGCTimer();
 
       // Avoid shrinking during heavy activity, which is suggested by
       // compartment GC.
       nsJSContext::PokeShrinkGCBuffers();
     }
   }
 
+  if ((aProgress == JS::GC_SLICE_END || aProgress == JS::GC_CYCLE_END) &&
+      ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
+    nsCycleCollector_dispatchDeferredDeletion();
+  }
+
   if (sPrevGCSliceCallback)
     (*sPrevGCSliceCallback)(aRt, aProgress, aDesc);
 }
 
 static void
 DOMAnalysisPurgeCallback(JSRuntime *aRt, JS::Handle<JSFlatString*> aDesc)
 {
   NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -81,16 +81,22 @@
     "high": "10000",
     "n_buckets": 50,
     "description": "Time spent on one ContentUnbinder (ms)"
   },
   "CYCLE_COLLECTOR_OOM": {
     "kind": "flag",
     "description": "Set if the cycle collector ran out of memory at some point"
   },
+  "CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": 50,
+    "description": "Time spent on one asynchronous SnowWhite freeing (ms)"
+  },
   "FORGET_SKIPPABLE_MAX": {
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 50,
     "description": "Max time spent on one forget skippable (ms)"
   },
   "GC_REASON_2": {
     "kind": "enumerated",
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -653,16 +653,18 @@ CanonicalizeParticipant(void **parti, ns
                      "Don't add objects that don't participate in collection!");
         nsXPCOMCycleCollectionParticipant *xcp;
         ToParticipant(nsparti, &xcp);
         *parti = nsparti;
         *cp = xcp;
     }
 }
 
+class nsCycleCollector;
+
 struct nsPurpleBuffer
 {
 private:
     struct Block {
         Block *mNext;
         // Try to match the size of a jemalloc bucket, to minimize slop bytes.
         // - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries
         //   is 16,380 bytes, which leaves 4 bytes for mNext.
@@ -770,24 +772,26 @@ public:
     void UnmarkRemainingPurple(Block *b)
     {
         UnmarkRemainingPurpleVisitor visitor;
         b->VisitEntries(*this, visitor);
     }
 
     void SelectPointers(GCGraphBuilder &builder);
 
-    // RemoveSkippable removes entries from the purple buffer if
-    // nsPurpleBufferEntry::mRefCnt is 0 or if the object's
-    // nsXPCOMCycleCollectionParticipant::CanSkip() returns true or
-    // if nsPurpleBufferEntry::mRefCnt->IsPurple() is false.
-    // If removeChildlessNodes is true, then any nodes in the purple buffer
-    // that will have no children in the cycle collector graph will also be
-    // removed. CanSkip() may be run on these children.
-    void RemoveSkippable(bool removeChildlessNodes,
+    // RemoveSkippable removes entries from the purple buffer synchronously
+    // (1) if aAsyncSnowWhiteFreeing is false and nsPurpleBufferEntry::mRefCnt is 0 or
+    // (2) if the object's nsXPCOMCycleCollectionParticipant::CanSkip() returns true or
+    // (3) if nsPurpleBufferEntry::mRefCnt->IsPurple() is false.
+    // (4) If removeChildlessNodes is true, then any nodes in the purple buffer
+    //     that will have no children in the cycle collector graph will also be
+    //     removed. CanSkip() may be run on these children.
+    void RemoveSkippable(nsCycleCollector* aCollector,
+                         bool removeChildlessNodes,
+                         bool aAsyncSnowWhiteFreeing,
                          CC_ForgetSkippableCallback aCb);
 
     nsPurpleBufferEntry* NewEntry()
     {
         if (!mFreeList) {
             Block *b = new Block;
             StartBlock(b);
 
@@ -969,16 +973,18 @@ public:
         thread->Shutdown();
     }
 };
 
 ////////////////////////////////////////////////////////////////////////
 // Top level structure for the cycle collector.
 ////////////////////////////////////////////////////////////////////////
 
+class AsyncFreeSnowWhite;
+
 class nsCycleCollector
 {
     friend class GCGraphBuilder;
 
     bool mCollectionInProgress;
     bool mScanInProgress;
     nsCycleCollectorResults *mResults;
     TimeStamp mCollectionStart;
@@ -990,16 +996,18 @@ class nsCycleCollector
     // Strong reference
     nsCycleCollectorRunner *mRunner;
     PRThread* mThread;
 
 public:
     nsCycleCollectorParams mParams;
 
 private:
+    nsRefPtr<AsyncFreeSnowWhite> mAsyncSnowWhiteFreer;
+
     nsTArray<PtrInfo*> *mWhiteNodes;
     uint32_t mWhiteNodeCount;
 
     // mVisitedRefCounted and mVisitedGCed are only used for telemetry
     uint32_t mVisitedRefCounted;
     uint32_t mVisitedGCed;
 
     CC_BeforeUnlinkCallback mBeforeUnlinkCB;
@@ -1032,17 +1040,17 @@ public:
         mForgetSkippableCB = aForgetSkippableCB;
     }
 
     void SelectPurple(GCGraphBuilder &builder);
     void MarkRoots(GCGraphBuilder &builder);
     void ScanRoots();
     void ScanWeakMaps();
 
-    void ForgetSkippable(bool removeChildlessNodes);
+    void ForgetSkippable(bool aRemoveChildlessNodes, bool aAsyncSnowWhiteFreeing);
 
     // returns whether anything was collected
     bool CollectWhite(nsICycleCollectorListener *aListener);
 
     nsCycleCollector(CCThreadingModel aModel);
     ~nsCycleCollector();
 
     nsresult Init();
@@ -1067,21 +1075,27 @@ public:
     void FixGrayBits(bool aForceGC);
     bool ShouldMergeZones(ccType aCCType);
     void CleanupAfterCollection();
 
     // Start and finish an individual collection.
     bool BeginCollection(ccType aCCType, nsICycleCollectorListener *aListener);
     bool FinishCollection(nsICycleCollectorListener *aListener);
 
-    void FreeSnowWhite(bool aUntilNoSWInPurpleBuffer);
+    AsyncFreeSnowWhite* AsyncSnowWhiteFreer()
+    {
+        return mAsyncSnowWhiteFreer;
+    }
+
+    bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer);
 
     // If there is a cycle collector available in the current thread,
-    // this calls FreeSnowWhite(false).
-    static void TryToFreeSnowWhite();
+    // this calls FreeSnowWhite(false). Returns true if some
+    // snow-white objects were found.
+    static bool TryToFreeSnowWhite();
 
     uint32_t SuspectedCount();
     void Shutdown();
 
     void ClearGraph()
     {
         mGraph.mNodes.Clear();
         mGraph.mEdges.Clear();
@@ -2158,16 +2172,54 @@ ChildFinder::NoteJSChild(void *child)
 static bool
 MayHaveChild(void *o, nsCycleCollectionParticipant* cp)
 {
     ChildFinder cf;
     cp->Traverse(o, cf);
     return cf.MayHaveChild();
 }
 
+class AsyncFreeSnowWhite : public nsRunnable
+{
+public:
+  NS_IMETHOD Run()
+  {
+      TimeStamp start = TimeStamp::Now();
+      bool hadSnowWhiteObjects = nsCycleCollector::TryToFreeSnowWhite();
+      Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING,
+                            uint32_t((TimeStamp::Now() - start).ToMilliseconds()));
+      if (hadSnowWhiteObjects && !mContinuation) {
+          mContinuation = true;
+          if (NS_FAILED(NS_DispatchToCurrentThread(this))) {
+              mActive = false;
+          }
+      } else {
+          mActive = false;
+      }
+      return NS_OK;
+  }
+
+  static void Dispatch(nsCycleCollector* aCollector, bool aContinuation = false)
+  {
+      AsyncFreeSnowWhite* swf = aCollector->AsyncSnowWhiteFreer();
+      if (swf->mContinuation) {
+          swf->mContinuation = aContinuation;
+      }
+      if (!swf->mActive && NS_SUCCEEDED(NS_DispatchToCurrentThread(swf))) {
+          swf->mActive = true;
+      }
+  }
+
+  AsyncFreeSnowWhite() : mContinuation(false), mActive(false) {}
+
+public:
+  bool mContinuation;
+  bool mActive;
+};
+
 struct SnowWhiteObject
 {
   void* mPointer;
   nsCycleCollectionParticipant* mParticipant;
   nsCycleCollectingAutoRefCnt* mRefCnt;
 };
 
 class SnowWhiteKiller
@@ -2218,118 +2270,123 @@ public:
     }
 private:
     FallibleTArray<SnowWhiteObject> mObjects;
 };
 
 class RemoveSkippableVisitor : public SnowWhiteKiller
 {
 public:
-    RemoveSkippableVisitor(uint32_t aMaxCount, bool aRemoveChildlessNodes,
+    RemoveSkippableVisitor(nsCycleCollector* aCollector,
+                           uint32_t aMaxCount, bool aRemoveChildlessNodes,
+                           bool aAsyncSnowWhiteFreeing,
                            CC_ForgetSkippableCallback aCb)
-        : SnowWhiteKiller(aMaxCount),
+        : SnowWhiteKiller(aAsyncSnowWhiteFreeing ? 0 : aMaxCount),
+          mCollector(aCollector),
           mRemoveChildlessNodes(aRemoveChildlessNodes),
+          mAsyncSnowWhiteFreeing(aAsyncSnowWhiteFreeing),
+          mDispatchedDeferredDeletion(false),
           mCallback(aCb)
     {}
 
     ~RemoveSkippableVisitor()
     {
         // Note, we must call the callback before SnowWhiteKiller calls
         // DeleteCycleCollectable!
         if (mCallback) {
             mCallback();
         }
+        if (HasSnowWhiteObjects()) {
+            // Effectively a continuation.
+            AsyncFreeSnowWhite::Dispatch(mCollector, true);
+        }
     }
 
     void
     Visit(nsPurpleBuffer &aBuffer, nsPurpleBufferEntry *aEntry)
     {
         MOZ_ASSERT(aEntry->mObject, "null mObject in purple buffer");
         if (!aEntry->mRefCnt->get()) {
-            SnowWhiteKiller::Visit(aBuffer, aEntry);
+            if (!mAsyncSnowWhiteFreeing) {
+                SnowWhiteKiller::Visit(aBuffer, aEntry);
+            } else if (!mDispatchedDeferredDeletion) {
+                mDispatchedDeferredDeletion = true;
+                nsCycleCollector_dispatchDeferredDeletion();
+            }
             return;
         }
         void *o = aEntry->mObject;
         nsCycleCollectionParticipant *cp = aEntry->mParticipant;
         CanonicalizeParticipant(&o, &cp);
         if (aEntry->mRefCnt->IsPurple() && !cp->CanSkip(o, false) &&
             (!mRemoveChildlessNodes || MayHaveChild(o, cp))) {
             return;
         }
         aBuffer.Remove(aEntry);
     }
 
 private:
+    nsCycleCollector* mCollector;
     bool mRemoveChildlessNodes;
+    bool mAsyncSnowWhiteFreeing;
+    bool mDispatchedDeferredDeletion;
     CC_ForgetSkippableCallback mCallback;
 };
 
 void
-nsPurpleBuffer::RemoveSkippable(bool removeChildlessNodes,
+nsPurpleBuffer::RemoveSkippable(nsCycleCollector* aCollector,
+                                bool aRemoveChildlessNodes,
+                                bool aAsyncSnowWhiteFreeing,
                                 CC_ForgetSkippableCallback aCb)
 {
-    RemoveSkippableVisitor visitor(Count(), removeChildlessNodes, aCb);
+    RemoveSkippableVisitor visitor(aCollector, Count(), aRemoveChildlessNodes,
+                                   aAsyncSnowWhiteFreeing, aCb);
     VisitEntries(visitor);
-    // If we're about to delete some objects when visitor goes out of scope,
-    // try to delete some more soon.
-    if (visitor.HasSnowWhiteObjects()) {
-        nsCycleCollector_dispatchDeferredDeletion();
-    }
 }
 
-class AsyncFreeSnowWhite : public nsRunnable
-{
-public:
-  NS_IMETHOD Run()
-  {
-      nsCycleCollector::TryToFreeSnowWhite();
-      return NS_OK;
-  }
-
-  static void Dispatch()
-  {
-      nsRefPtr<AsyncFreeSnowWhite> ev = new AsyncFreeSnowWhite();
-      NS_DispatchToCurrentThread(ev);
-  }
-};
-
-void
+bool
 nsCycleCollector::FreeSnowWhite(bool aUntilNoSWInPurpleBuffer)
 {
+    bool hadSnowWhiteObjects = false;
     do {
         SnowWhiteKiller visitor(mPurpleBuf.Count());
         mPurpleBuf.VisitEntries(visitor);
+        hadSnowWhiteObjects = hadSnowWhiteObjects ||
+                              visitor.HasSnowWhiteObjects();
         if (!visitor.HasSnowWhiteObjects()) {
             break;
         }
     } while (aUntilNoSWInPurpleBuffer);
+    return hadSnowWhiteObjects;
 }
 
-/* static */ void
+/* static */ bool
 nsCycleCollector::TryToFreeSnowWhite()
 {
   CollectorData* data = sCollectorData.get();
-  if (data->mCollector) {
-      data->mCollector->FreeSnowWhite(false);
-  }
+  return data->mCollector ?
+      data->mCollector->FreeSnowWhite(false) :
+      false;
 }
 
 void
 nsCycleCollector::SelectPurple(GCGraphBuilder &builder)
 {
     mPurpleBuf.SelectPointers(builder);
 }
 
 void
-nsCycleCollector::ForgetSkippable(bool removeChildlessNodes)
+nsCycleCollector::ForgetSkippable(bool aRemoveChildlessNodes,
+                                  bool aAsyncSnowWhiteFreeing)
 {
     if (mJSRuntime) {
         mJSRuntime->PrepareForForgetSkippable();
     }
-    mPurpleBuf.RemoveSkippable(removeChildlessNodes, mForgetSkippableCB);
+    mPurpleBuf.RemoveSkippable(this, aRemoveChildlessNodes,
+                               aAsyncSnowWhiteFreeing, mForgetSkippableCB);
 }
 
 MOZ_NEVER_INLINE void
 nsCycleCollector::MarkRoots(GCGraphBuilder &builder)
 {
     mGraph.mRootCount = builder.Count();
 
     // read the PtrInfo out of the graph that we are building
@@ -2646,16 +2703,17 @@ NS_IMPL_ISUPPORTS1(CycleCollectorMultiRe
 
 nsCycleCollector::nsCycleCollector(CCThreadingModel aModel) :
     mCollectionInProgress(false),
     mScanInProgress(false),
     mResults(nullptr),
     mJSRuntime(nullptr),
     mRunner(nullptr),
     mThread(PR_GetCurrentThread()),
+    mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()),
     mWhiteNodes(nullptr),
     mWhiteNodeCount(0),
     mVisitedRefCounted(0),
     mVisitedGCed(0),
     mBeforeUnlinkCB(nullptr),
     mForgetSkippableCB(nullptr),
     mReporter(nullptr),
     mUnmergedNeeded(0),
@@ -3267,34 +3325,39 @@ nsCycleCollector_setForgetSkippableCallb
     // We should have started the cycle collector by now.
     MOZ_ASSERT(data);
     MOZ_ASSERT(data->mCollector);
 
     data->mCollector->SetForgetSkippableCallback(aCB);
 }
 
 void
-nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes)
+nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes,
+                                 bool aAsyncSnowWhiteFreeing)
 {
     CollectorData *data = sCollectorData.get();
 
     // We should have started the cycle collector by now.
     MOZ_ASSERT(data);
     MOZ_ASSERT(data->mCollector);
 
     PROFILER_LABEL("CC", "nsCycleCollector_forgetSkippable");
     TimeLog timeLog;
-    data->mCollector->ForgetSkippable(aRemoveChildlessNodes);
+    data->mCollector->ForgetSkippable(aRemoveChildlessNodes,
+                                      aAsyncSnowWhiteFreeing);
     timeLog.Checkpoint("ForgetSkippable()");
 }
 
 void
 nsCycleCollector_dispatchDeferredDeletion()
 {
-    AsyncFreeSnowWhite::Dispatch();
+    CollectorData* data = sCollectorData.get();
+    if (data && data->mCollector) {
+        AsyncFreeSnowWhite::Dispatch(data->mCollector);
+    }
 }
 
 void
 nsCycleCollector_collect(bool aManuallyTriggered,
                          nsCycleCollectorResults *aResults,
                          nsICycleCollectorListener *aListener)
 {
     CollectorData *data = sCollectorData.get();
--- a/xpcom/base/nsCycleCollector.h
+++ b/xpcom/base/nsCycleCollector.h
@@ -49,17 +49,18 @@ enum CCThreadingModel {
 nsresult nsCycleCollector_startup(CCThreadingModel aThreadingModel);
 
 typedef void (*CC_BeforeUnlinkCallback)(void);
 void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB);
 
 typedef void (*CC_ForgetSkippableCallback)(void);
 void nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB);
 
-void nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes = false);
+void nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes = false,
+                                      bool aAsyncSnowWhiteFreeing = false);
 
 void nsCycleCollector_dispatchDeferredDeletion();
 
 void nsCycleCollector_collect(bool aManuallyTriggered,
                               nsCycleCollectorResults *aResults,
                               nsICycleCollectorListener *aListener);
 uint32_t nsCycleCollector_suspectedCount();
 void nsCycleCollector_shutdownThreads();