Bug 1391423, add a nursery for purple buffer to allow faster addref/release on the main thread, r=mccr8
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Tue, 22 Aug 2017 00:01:47 +0300
changeset 376071 f63a7cba6c08
parent 376070 8827ea5e0a50
child 376072 d824da178034
push id32372
push userarchaeopteryx@coole-files.de
push dateTue, 22 Aug 2017 09:49:24 +0000
treeherdermozilla-central@eb72c8c07751 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1391423
milestone57.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 1391423, add a nursery for purple buffer to allow faster addref/release on the main thread, r=mccr8
dom/base/FragmentOrElement.cpp
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsISupportsImpl.h
xpcom/build/nsXPCOM.h
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -2139,19 +2139,19 @@ NS_INTERFACE_MAP_BEGIN(FragmentOrElement
   NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
   NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
                                  new nsNodeSupportsWeakRefTearoff(this))
   // DOM bindings depend on the identity pointer being the
   // same as nsINode (which nsIContent inherits).
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(FragmentOrElement)
-NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(FragmentOrElement,
-                                                   nsNodeUtils::LastRelease(this))
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(FragmentOrElement)
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(FragmentOrElement,
+                                                                    nsNodeUtils::LastRelease(this))
 
 //----------------------------------------------------------------------
 
 nsresult
 FragmentOrElement::CopyInnerTo(FragmentOrElement* aDst,
                                bool aPreallocateChildren)
 {
   nsresult rv = aDst->mAttrsAndChildren.EnsureCapacityToClone(mAttrsAndChildren,
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -192,16 +192,45 @@
 #include "mozilla/ThreadLocal.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 using namespace mozilla;
 
+struct NurseryPurpleBufferEntry
+{
+  void* mPtr;
+  nsCycleCollectionParticipant* mParticipant;
+  nsCycleCollectingAutoRefCnt* mRefCnt;
+};
+
+#define NURSERY_PURPLE_BUFFER_SIZE 2048
+bool gNurseryPurpleBufferEnabled = true;
+NurseryPurpleBufferEntry gNurseryPurpleBufferEntry[NURSERY_PURPLE_BUFFER_SIZE];
+uint32_t gNurseryPurpleBufferEntryCount = 0;
+
+void ClearNurseryPurpleBuffer();
+
+void SuspectUsingNurseryPurpleBuffer(void* aPtr,
+                                     nsCycleCollectionParticipant* aCp,
+                                     nsCycleCollectingAutoRefCnt* aRefCnt)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  MOZ_ASSERT(gNurseryPurpleBufferEnabled);
+  if (gNurseryPurpleBufferEntryCount == NURSERY_PURPLE_BUFFER_SIZE) {
+    ClearNurseryPurpleBuffer();
+  }
+
+  gNurseryPurpleBufferEntry[gNurseryPurpleBufferEntryCount] =
+    { aPtr, aCp, aRefCnt };
+  ++gNurseryPurpleBufferEntryCount;
+}
+
 //#define COLLECT_TIME_DEBUG
 
 // Enable assertions that are useful for diagnosing errors in graph construction.
 //#define DEBUG_CC_GRAPH
 
 #define DEFAULT_SHUTDOWN_COLLECTIONS 5
 
 // One to do the freeing, then another to detect there is no more work to do.
@@ -1034,16 +1063,23 @@ public:
   ~nsPurpleBuffer()
   {
   }
 
   // This method compacts mEntries.
   template<class PurpleVisitor>
   void VisitEntries(PurpleVisitor& aVisitor)
   {
+    Maybe<AutoRestore<bool>> ar;
+    if (NS_IsMainThread()) {
+      ar.emplace(gNurseryPurpleBufferEnabled);
+      gNurseryPurpleBufferEnabled = false;
+      ClearNurseryPurpleBuffer();
+    }
+
     if (mEntries.IsEmpty()) {
       return;
     }
 
     uint32_t oldLength = mEntries.Length();
     uint32_t newLength = 0;
     auto revIter = mEntries.IterFromLast();
     auto iter = mEntries.Iter();
@@ -1284,16 +1320,17 @@ public:
   void SetForgetSkippableCallback(CC_ForgetSkippableCallback aForgetSkippableCB)
   {
     CheckThreadSafety();
     mForgetSkippableCB = aForgetSkippableCB;
   }
 
   void Suspect(void* aPtr, nsCycleCollectionParticipant* aCp,
                nsCycleCollectingAutoRefCnt* aRefCnt);
+  void SuspectNurseryEntries();
   uint32_t SuspectedCount();
   void ForgetSkippable(js::SliceBudget& aBudget, bool aRemoveChildlessNodes,
                        bool aAsyncSnowWhiteFreeing);
   bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer);
 
   // This method assumes its argument is already canonicalized.
   void RemoveObjectFromGraph(void* aPtr);
 
@@ -3487,16 +3524,27 @@ nsCycleCollector::Suspect(void* aPtr, ns
 
   MOZ_ASSERT(aParti || CanonicalizeXPCOMParticipant(static_cast<nsISupports*>(aPtr)) == aPtr,
              "Suspect nsISupports pointer must be canonical");
 
   mPurpleBuf.Put(aPtr, aParti, aRefCnt);
 }
 
 void
+nsCycleCollector::SuspectNurseryEntries()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  while (gNurseryPurpleBufferEntryCount) {
+    NurseryPurpleBufferEntry& entry =
+      gNurseryPurpleBufferEntry[--gNurseryPurpleBufferEntryCount];
+    mPurpleBuf.Put(entry.mPtr, entry.mParticipant, entry.mRefCnt);
+  }
+}
+
+void
 nsCycleCollector::CheckThreadSafety()
 {
 #ifdef DEBUG
   MOZ_ASSERT(mEventTarget->IsOnCurrentThread());
 #endif
 }
 
 // The cycle collector uses the mark bitmap to discover what JS objects
@@ -3888,24 +3936,32 @@ nsCycleCollector::BeginCollection(ccType
   mBuilder->DoneAddingRoots();
   mIncrementalPhase = GraphBuildingPhase;
 }
 
 uint32_t
 nsCycleCollector::SuspectedCount()
 {
   CheckThreadSafety();
+  if (NS_IsMainThread()) {
+    return gNurseryPurpleBufferEntryCount + mPurpleBuf.Count();
+  }
+
   return mPurpleBuf.Count();
 }
 
 void
 nsCycleCollector::Shutdown(bool aDoCollect)
 {
   CheckThreadSafety();
 
+  if (NS_IsMainThread()) {
+    gNurseryPurpleBufferEnabled = false;
+  }
+
   // Always delete snow white objects.
   FreeSnowWhite(true);
 
   if (aDoCollect) {
     ShutdownCollect();
   }
 }
 
@@ -4033,16 +4089,40 @@ NS_CycleCollectorSuspect3(void* aPtr, ns
 
   if (MOZ_LIKELY(data->mCollector)) {
     data->mCollector->Suspect(aPtr, aCp, aRefCnt);
     return;
   }
   SuspectAfterShutdown(aPtr, aCp, aRefCnt, aShouldDelete);
 }
 
+void ClearNurseryPurpleBuffer()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  CollectorData* data = sCollectorData.get();
+  MOZ_ASSERT(data);
+  MOZ_ASSERT(data->mCollector);
+  data->mCollector->SuspectNurseryEntries();
+}
+
+void
+NS_CycleCollectorSuspectUsingNursery(void* aPtr,
+                                     nsCycleCollectionParticipant* aCp,
+                                     nsCycleCollectingAutoRefCnt* aRefCnt,
+                                     bool* aShouldDelete)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  if (!gNurseryPurpleBufferEnabled) {
+    NS_CycleCollectorSuspect3(aPtr, aCp, aRefCnt, aShouldDelete);
+    return;
+  }
+
+  SuspectUsingNurseryPurpleBuffer(aPtr, aCp, aRefCnt);
+}
+
 uint32_t
 nsCycleCollector_suspectedCount()
 {
   CollectorData* data = sCollectorData.get();
 
   // We should have started the cycle collector by now.
   MOZ_ASSERT(data);
 
--- a/xpcom/base/nsISupportsImpl.h
+++ b/xpcom/base/nsISupportsImpl.h
@@ -185,71 +185,81 @@ do {                                    
 #define NS_IN_PURPLE_BUFFER (1 << 0)
 #define NS_IS_PURPLE (1 << 1)
 #define NS_REFCOUNT_CHANGE (1 << NS_NUMBER_OF_FLAGS_IN_REFCNT)
 #define NS_REFCOUNT_VALUE(_val) (_val >> NS_NUMBER_OF_FLAGS_IN_REFCNT)
 
 class nsCycleCollectingAutoRefCnt
 {
 public:
+
+  typedef void (*Suspect)(void* aPtr,
+                          nsCycleCollectionParticipant* aCp,
+                          nsCycleCollectingAutoRefCnt* aRefCnt,
+                          bool* aShouldDelete);
+
   nsCycleCollectingAutoRefCnt() : mRefCntAndFlags(0) {}
 
   explicit nsCycleCollectingAutoRefCnt(uintptr_t aValue)
     : mRefCntAndFlags(aValue << NS_NUMBER_OF_FLAGS_IN_REFCNT)
   {
   }
 
   nsCycleCollectingAutoRefCnt(const nsCycleCollectingAutoRefCnt&) = delete;
   void operator=(const nsCycleCollectingAutoRefCnt&) = delete;
 
+  template<Suspect suspect = NS_CycleCollectorSuspect3>
   MOZ_ALWAYS_INLINE uintptr_t incr(nsISupports* aOwner)
   {
-    return incr(aOwner, nullptr);
+    return incr<suspect>(aOwner, nullptr);
   }
 
+  template<Suspect suspect = NS_CycleCollectorSuspect3>
   MOZ_ALWAYS_INLINE uintptr_t incr(void* aOwner,
                                    nsCycleCollectionParticipant* aCp)
   {
     mRefCntAndFlags += NS_REFCOUNT_CHANGE;
     mRefCntAndFlags &= ~NS_IS_PURPLE;
     // For incremental cycle collection, use the purple buffer to track objects
     // that have been AddRef'd.
     if (!IsInPurpleBuffer()) {
       mRefCntAndFlags |= NS_IN_PURPLE_BUFFER;
       // Refcount isn't zero, so Suspect won't delete anything.
       MOZ_ASSERT(get() > 0);
-      NS_CycleCollectorSuspect3(aOwner, aCp, this, nullptr);
+      suspect(aOwner, aCp, this, nullptr);
     }
     return NS_REFCOUNT_VALUE(mRefCntAndFlags);
   }
 
   MOZ_ALWAYS_INLINE void stabilizeForDeletion()
   {
     // Set refcnt to 1 and mark us to be in the purple buffer.
     // This way decr won't call suspect again.
     mRefCntAndFlags = NS_REFCOUNT_CHANGE | NS_IN_PURPLE_BUFFER;
   }
 
+  template<Suspect suspect = NS_CycleCollectorSuspect3>
   MOZ_ALWAYS_INLINE uintptr_t decr(nsISupports* aOwner,
                                    bool* aShouldDelete = nullptr)
   {
-    return decr(aOwner, nullptr, aShouldDelete);
+    return decr<suspect>(aOwner, nullptr, aShouldDelete);
   }
 
+  template<Suspect suspect = NS_CycleCollectorSuspect3>
   MOZ_ALWAYS_INLINE uintptr_t decr(void* aOwner,
                                    nsCycleCollectionParticipant* aCp,
                                    bool* aShouldDelete = nullptr)
   {
     MOZ_ASSERT(get() > 0);
     if (!IsInPurpleBuffer()) {
       mRefCntAndFlags -= NS_REFCOUNT_CHANGE;
       mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE);
       uintptr_t retval = NS_REFCOUNT_VALUE(mRefCntAndFlags);
       // Suspect may delete 'aOwner' and 'this'!
-      NS_CycleCollectorSuspect3(aOwner, aCp, this, aShouldDelete);
+      suspect(aOwner, aCp, this, aShouldDelete);
       return retval;
     }
     mRefCntAndFlags -= NS_REFCOUNT_CHANGE;
     mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE);
     return NS_REFCOUNT_VALUE(mRefCntAndFlags);
   }
 
   MOZ_ALWAYS_INLINE void RemovePurple()
@@ -705,16 +715,28 @@ NS_IMETHODIMP_(MozExternalRefCountType) 
   MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");                        \
   NS_ASSERT_OWNINGTHREAD(_class);                                             \
   nsISupports *base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this);    \
   nsrefcnt count = mRefCnt.incr(base);                                        \
   NS_LOG_ADDREF(this, count, #_class, sizeof(*this));                         \
   return count;                                                               \
 }
 
+#define NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(_class)              \
+NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void)                  \
+{                                                                             \
+  MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class)                                  \
+  MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");                        \
+  NS_ASSERT_OWNINGTHREAD(_class);                                             \
+  nsISupports *base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this);    \
+  nsrefcnt count = mRefCnt.incr<NS_CycleCollectorSuspectUsingNursery>(base);  \
+  NS_LOG_ADDREF(this, count, #_class, sizeof(*this));                         \
+  return count;                                                               \
+}
+
 #define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(_class, _destroy)       \
 NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void)                 \
 {                                                                             \
   MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");                            \
   NS_ASSERT_OWNINGTHREAD(_class);                                             \
   nsISupports *base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this);    \
   nsrefcnt count = mRefCnt.decr(base);                                        \
   NS_LOG_RELEASE(this, count, #_class);                                       \
@@ -750,16 +772,43 @@ NS_IMETHODIMP_(MozExternalRefCountType) 
   }                                                                           \
   return count;                                                               \
 }                                                                             \
 NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void)                     \
 {                                                                             \
   delete this;                                                                \
 }
 
+// _LAST_RELEASE can be useful when certain resources should be released
+// as soon as we know the object will be deleted.
+#define NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(_class, _last)  \
+NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void)                               \
+{                                                                                           \
+  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");                                          \
+  NS_ASSERT_OWNINGTHREAD(_class);                                                           \
+  bool shouldDelete = false;                                                                \
+  nsISupports *base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this);                  \
+  nsrefcnt count = mRefCnt.decr<NS_CycleCollectorSuspectUsingNursery>(base, &shouldDelete); \
+  NS_LOG_RELEASE(this, count, #_class);                                                     \
+  if (count == 0) {                                                                         \
+      mRefCnt.incr<NS_CycleCollectorSuspectUsingNursery>(base);                             \
+      _last;                                                                                \
+      mRefCnt.decr<NS_CycleCollectorSuspectUsingNursery>(base);                             \
+      if (shouldDelete) {                                                                   \
+          mRefCnt.stabilizeForDeletion();                                                   \
+          DeleteCycleCollectable();                                                         \
+      }                                                                                     \
+  }                                                                                         \
+  return count;                                                                             \
+}                                                                                           \
+NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void)                                   \
+{                                                                                           \
+  delete this;                                                                              \
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 /**
  * There are two ways of implementing QueryInterface, and we use both:
  *
  * Table-driven QueryInterface uses a static table of IID->offset mappings
  * and a shared helper function. Using it tends to reduce codesize and improve
  * runtime performance (due to processor cache hits).
--- a/xpcom/build/nsXPCOM.h
+++ b/xpcom/build/nsXPCOM.h
@@ -353,16 +353,22 @@ XPCOM_API(void) NS_LogCOMPtrRelease(void
 class nsCycleCollectionParticipant;
 class nsCycleCollectingAutoRefCnt;
 
 XPCOM_API(void) NS_CycleCollectorSuspect3(void* aPtr,
                                           nsCycleCollectionParticipant* aCp,
                                           nsCycleCollectingAutoRefCnt* aRefCnt,
                                           bool* aShouldDelete);
 
+XPCOM_API(void)
+NS_CycleCollectorSuspectUsingNursery(void* aPtr,
+                                     nsCycleCollectionParticipant* aCp,
+                                     nsCycleCollectingAutoRefCnt* aRefCnt,
+                                     bool* aShouldDelete);
+
 #endif
 
 /**
  * Categories (in the category manager service) used by XPCOM:
  */
 
 /**
  * A category which is read after component registration but before