Bug 819817 - DMD: cache calls to NS_DescribeCodeAddress for faster dumping. r=jlebar.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 13 Dec 2012 16:58:28 -0800
changeset 125303 e5fddab2f19e10b86fc95d5a273e5bbbd7680adc
parent 125302 fefbcfe3575a126795e3f478431950cf7ff17e2b
child 125304 e082861f545f96949f4afa0aeaccddd798fd300c
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar
bugs819817
milestone20.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 819817 - DMD: cache calls to NS_DescribeCodeAddress for faster dumping. r=jlebar.
memory/replace/dmd/DMD.cpp
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -119,18 +119,17 @@ public:
     ExitOnFailure(p);
     return p;
   }
 
   static void free_(void* aPtr) { gMallocTable->free(aPtr); }
 
   static char* strdup_(const char* aStr)
   {
-    char* s = (char*) gMallocTable->malloc(strlen(aStr) + 1);
-    ExitOnFailure(s);
+    char* s = (char*) InfallibleAllocPolicy::malloc_(strlen(aStr) + 1);
     strcpy(s, aStr);
     return s;
   }
 
   template <class T>
   static T* new_()
   {
     void* mem = malloc_(sizeof(T));
@@ -141,16 +140,25 @@ public:
   template <class T, typename P1>
   static T* new_(P1 p1)
   {
     void* mem = malloc_(sizeof(T));
     ExitOnFailure(mem);
     return new (mem) T(p1);
   }
 
+  template <class T>
+  static void delete_(T *p)
+  {
+    if (p) {
+      p->~T();
+      InfallibleAllocPolicy::free_(p);
+    }
+  }
+
   static void reportAllocOverflow() { ExitOnFailure(nullptr); }
 };
 
 // This is only needed because of the |const void*| vs |void*| arg mismatch.
 static size_t
 MallocSizeOf(const void* aPtr)
 {
   return gMallocTable->malloc_usable_size(const_cast<void*>(aPtr));
@@ -489,36 +497,202 @@ public:
   ~AutoBlockIntercepts()
   {
     MOZ_ASSERT(mT->interceptsAreBlocked());
     mT->unblockIntercepts();
   }
 };
 
 //---------------------------------------------------------------------------
+// Location service
+//---------------------------------------------------------------------------
+
+// This class is used to print details about code locations.
+class LocationService
+{
+  // WriteLocation() is the key function in this class.  It's basically a
+  // wrapper around NS_DescribeCodeAddress.
+  //
+  // However, NS_DescribeCodeAddress is very slow on some platforms, and we
+  // have lots of repeated (i.e. same PC) calls to it.  So we do some caching
+  // of results.  Each cached result includes two strings (|mFunction| and
+  // |mLibrary|), so we also optimize them for space in the following ways.
+  //
+  // - The number of distinct library names is small, e.g. a few dozen.  There
+  //   is lots of repetition, especially of libxul.  So we intern them in their
+  //   own table, which saves space over duplicating them for each cache entry.
+  //
+  // - The number of distinct function names is much higher, so we duplicate
+  //   them in each cache entry.  That's more space-efficient than interning
+  //   because entries containing single-occurrence function names are quickly
+  //   overwritten, and their copies released.  In addition, empty function
+  //   names are common, so we use nullptr to represent them compactly.
+
+  struct StringHasher
+  {
+      typedef const char* Lookup;
+
+      static uint32_t hash(const char* const& aS)
+      {
+          return HashString(aS);
+      }
+
+      static bool match(const char* const& aA, const char* const& aB)
+      {
+          return strcmp(aA, aB) == 0;
+      }
+  };
+
+  typedef js::HashSet<const char*, StringHasher, InfallibleAllocPolicy>
+          StringTable;
+
+  StringTable mLibraryStrings;
+
+  struct Entry
+  {
+    const void* mPc;        // the entry is unused if this is null
+    char*       mFunction;  // owned by the Entry;  may be null
+    const char* mLibrary;   // owned by mLibraryStrings;  never null
+                            //   in a non-empty entry is in use
+    ptrdiff_t   mLOffset;
+
+    Entry()
+      : mPc(nullptr), mFunction(nullptr), mLibrary(nullptr), mLOffset(0)
+    {}
+
+    ~Entry()
+    {
+      // We don't free mLibrary because it's externally owned.
+      InfallibleAllocPolicy::free_(mFunction);
+    }
+
+    void Replace(const void* aPc, const char* aFunction, const char* aLibrary,
+                 ptrdiff_t aLOffset)
+    {
+      mPc = aPc;
+
+      // Convert "" to nullptr.  Otherwise, make a copy of the name.
+      InfallibleAllocPolicy::free_(mFunction);
+      mFunction =
+        !aFunction[0] ? nullptr : InfallibleAllocPolicy::strdup_(aFunction);
+
+      mLibrary = aLibrary;
+      mLOffset = aLOffset;
+    }
+
+    size_t SizeOfExcludingThis() {
+      // Don't measure mLibrary because it's externally owned.
+      return MallocSizeOf(mFunction);
+    }
+  };
+
+  // A direct-mapped cache.  When doing a dump just after starting desktop
+  // Firefox (which is similar to dumping after a longer-running session,
+  // thanks to the limit on how many groups we dump), a cache with 2^24 entries
+  // (which approximates an infinite-entry cache) has a ~91% hit rate.  A cache
+  // with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB (on 32-bit
+  // platforms) or ~150 KiB (on 64-bit platforms).
+  static const size_t kNumEntries = 1 << 12;
+  static const size_t kMask = kNumEntries - 1;
+  Entry mEntries[kNumEntries];
+
+  size_t mNumCacheHits;
+  size_t mNumCacheMisses;
+
+public:
+  LocationService()
+    : mEntries(), mNumCacheHits(0), mNumCacheMisses(0)
+  {
+    (void)mLibraryStrings.init(64);
+  }
+
+  void WriteLocation(const Writer& aWriter, const void* aPc)
+  {
+    MOZ_ASSERT(gStateLock->IsLocked());
+
+    uint32_t index = HashGeneric(aPc) & kMask;
+    MOZ_ASSERT(index < kNumEntries);
+    Entry& entry = mEntries[index];
+
+    MOZ_ASSERT(aPc);    // important, because null represents an empty entry
+    if (entry.mPc != aPc) {
+      mNumCacheMisses++;
+
+      // NS_DescribeCodeAddress can (on Linux) acquire a lock inside
+      // the shared library loader.  Another thread might call malloc
+      // while holding that lock (when loading a shared library).  So
+      // we have to exit gStateLock around this call.  For details, see
+      // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3
+      nsCodeAddressDetails details;
+      {
+        AutoUnlockState unlock;
+        (void)NS_DescribeCodeAddress(const_cast<void*>(aPc), &details);
+      }
+
+      // Intern the library name.
+      const char* library = nullptr;
+      StringTable::AddPtr p = mLibraryStrings.lookupForAdd(details.library);
+      if (!p) {
+        library = InfallibleAllocPolicy::strdup_(details.library);
+        (void)mLibraryStrings.add(p, library);
+      } else {
+        library = *p;
+      }
+
+      entry.Replace(aPc, details.function, library, details.loffset);
+
+    } else {
+      mNumCacheHits++;
+    }
+
+    MOZ_ASSERT(entry.mPc == aPc);
+
+    // Use "???" for unknown functions.
+    W("   %s[%s +0x%X] %p\n", entry.mFunction ? entry.mFunction : "???",
+      entry.mLibrary, entry.mLOffset, entry.mPc);
+  }
+
+  size_t SizeOfIncludingThis()
+  {
+    size_t n = MallocSizeOf(this);
+    for (uint32_t i = 0; i < kNumEntries; i++) {
+      n += mEntries[i].SizeOfExcludingThis();
+    }
+
+    n += mLibraryStrings.sizeOfExcludingThis(MallocSizeOf);
+    for (StringTable::Range r = mLibraryStrings.all();
+         !r.empty();
+         r.popFront()) {
+      n += MallocSizeOf(r.front());
+    }
+
+    return n;
+  }
+
+  size_t CacheCapacity() const { return kNumEntries; }
+
+  size_t CacheCount() const
+  {
+    size_t n = 0;
+    for (size_t i = 0; i < kNumEntries; i++) {
+      if (mEntries[i].mPc) {
+        n++;
+      }
+    }
+    return n;
+  }
+
+  size_t NumCacheHits()   const { return mNumCacheHits; }
+  size_t NumCacheMisses() const { return mNumCacheMisses; }
+};
+
+//---------------------------------------------------------------------------
 // Stack traces
 //---------------------------------------------------------------------------
 
-static void
-PcInfo(const void* aPc, nsCodeAddressDetails* aDetails)
-{
-  // NS_DescribeCodeAddress can (on Linux) acquire a lock inside
-  // the shared library loader.  Another thread might call malloc
-  // while holding that lock (when loading a shared library).  So
-  // we have to exit gStateLock around this call.  For details, see
-  // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3
-  {
-    AutoUnlockState unlock;
-    (void)NS_DescribeCodeAddress(const_cast<void*>(aPc), aDetails);
-  }
-  if (!aDetails->function[0]) {
-    strcpy(aDetails->function, "???");
-  }
-}
-
 class StackTrace
 {
   static const uint32_t MaxDepth = 24;
 
   uint32_t mLength;             // The number of PCs.
   void* mPcs[MaxDepth];         // The PCs themselves.
 
 public:
@@ -533,17 +707,17 @@ public:
   // and so is immortal and unmovable.
   static const StackTrace* Get(Thread* aT);
 
   void Sort()
   {
     qsort(mPcs, mLength, sizeof(mPcs[0]), StackTrace::QsortCmp);
   }
 
-  void Print(const Writer& aWriter) const;
+  void Print(const Writer& aWriter, LocationService* aLocService) const;
 
   // Hash policy.
 
   typedef StackTrace* Lookup;
 
   static uint32_t hash(const StackTrace* const& aSt)
   {
     return mozilla::HashBytes(aSt->mPcs, aSt->Size());
@@ -579,37 +753,32 @@ private:
   }
 };
 
 typedef js::HashSet<StackTrace*, StackTrace, InfallibleAllocPolicy>
         StackTraceTable;
 static StackTraceTable* gStackTraceTable = nullptr;
 
 void
-StackTrace::Print(const Writer& aWriter) const
+StackTrace::Print(const Writer& aWriter, LocationService* aLocService) const
 {
   if (mLength == 0) {
     W("   (empty)\n");
     return;
   }
 
   if (gMode == Test) {
     // Don't print anything because there's too much variation.
     W("   (stack omitted due to test mode)\n");
     return;
   }
 
   for (uint32_t i = 0; i < mLength; i++) {
-    nsCodeAddressDetails details;
     void* pc = mPcs[i];
-    PcInfo(pc, &details);
-    if (details.function[0]) {
-      W("   %s[%s +0x%X] %p\n", details.function, details.library,
-        details.loffset, pc);
-    }
+    aLocService->WriteLocation(aWriter, pc);
   }
 }
 
 /* static */ const StackTrace*
 StackTrace::Get(Thread* aT)
 {
   MOZ_ASSERT(gStateLock->IsLocked());
   MOZ_ASSERT(aT->interceptsAreBlocked());
@@ -1161,18 +1330,18 @@ class LiveBlockGroup : public LiveBlockK
   friend class FrameGroup;      // FrameGroups are created from LiveBlockGroups
 
 public:
   explicit LiveBlockGroup(const LiveBlockKey& aKey)
     : LiveBlockKey(aKey),
       BlockGroup()
   {}
 
-  void Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
-             const char* aStr, const char* astr,
+  void Print(const Writer& aWriter, LocationService* aLocService,
+             uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
              size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
              size_t aTotalUsableSize) const;
 
   static int QsortCmp(const void* aA, const void* aB)
   {
     const LiveBlockGroup* const a =
       *static_cast<const LiveBlockGroup* const*>(aA);
     const LiveBlockGroup* const b =
@@ -1181,17 +1350,18 @@ public:
     return GroupSize::Cmp(a->mGroupSize, b->mGroupSize);
   }
 };
 
 typedef js::HashSet<LiveBlockGroup, LiveBlockGroup, InfallibleAllocPolicy>
         LiveBlockGroupTable;
 
 void
-LiveBlockGroup::Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
+LiveBlockGroup::Print(const Writer& aWriter, LocationService* aLocService,
+                      uint32_t aM, uint32_t aN,
                       const char* aStr, const char* astr,
                       size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
                       size_t aTotalUsableSize) const
 {
   bool showTilde = mGroupSize.IsSampled();
 
   W("%s: %s block%s in block group %s of %s\n",
     aStr,
@@ -1208,38 +1378,38 @@ LiveBlockGroup::Print(const Writer& aWri
     " %4.2f%% of %s (%4.2f%% cumulative)\n",
     Percent(mGroupSize.Usable(), aTotalUsableSize),
     Percent(aCumulativeUsableSize, aTotalUsableSize),
     Percent(mGroupSize.Usable(), aCategoryUsableSize),
     astr,
     Percent(aCumulativeUsableSize, aCategoryUsableSize));
 
   W(" Allocated at\n");
-  mAllocStackTrace->Print(aWriter);
+  mAllocStackTrace->Print(aWriter, aLocService);
 
   if (IsReported()) {
     W("\n Reported by '%s' at\n", mReporterName);
-    mReportStackTrace->Print(aWriter);
+    mReportStackTrace->Print(aWriter, aLocService);
   }
 
   W("\n");
 }
 
 // A group of one or more double-reported heap blocks with a common
 // DoubleReportBlockKey.
 class DoubleReportBlockGroup : public DoubleReportBlockKey, public BlockGroup
 {
 public:
   explicit DoubleReportBlockGroup(const DoubleReportBlockKey& aKey)
     : DoubleReportBlockKey(aKey),
       BlockGroup()
   {}
 
-  void Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
-             const char* aStr, const char* astr,
+  void Print(const Writer& aWriter, LocationService* aLocService,
+             uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
              size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
              size_t aTotalUsableSize) const;
 
   static int QsortCmp(const void* aA, const void* aB)
   {
     const DoubleReportBlockGroup* const a =
       *static_cast<const DoubleReportBlockGroup* const*>(aA);
     const DoubleReportBlockGroup* const b =
@@ -1249,17 +1419,19 @@ public:
   }
 };
 
 typedef js::HashSet<DoubleReportBlockGroup, DoubleReportBlockGroup,
                     InfallibleAllocPolicy> DoubleReportBlockGroupTable;
 DoubleReportBlockGroupTable* gDoubleReportBlockGroupTable = nullptr;
 
 void
-DoubleReportBlockGroup::Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
+DoubleReportBlockGroup::Print(const Writer& aWriter,
+                              LocationService* aLocService,
+                              uint32_t aM, uint32_t aN,
                               const char* aStr, const char* astr,
                               size_t aCategoryUsableSize,
                               size_t aCumulativeUsableSize,
                               size_t aTotalUsableSize) const
 {
   bool showTilde = mGroupSize.IsSampled();
 
   W("%s: %s block%s in block group %s of %s\n",
@@ -1269,23 +1441,23 @@ DoubleReportBlockGroup::Print(const Writ
     Show(aN, gBuf3, kBufLen));
 
   W(" %s bytes (%s requested / %s slop)\n",
     Show(mGroupSize.Usable(), gBuf1, kBufLen, showTilde),
     Show(mGroupSize.Req(),    gBuf2, kBufLen, showTilde),
     Show(mGroupSize.Slop(),   gBuf3, kBufLen, showTilde));
 
   W(" Allocated at\n");
-  mAllocStackTrace->Print(aWriter);
+  mAllocStackTrace->Print(aWriter, aLocService);
 
   W("\n Previously reported by '%s' at\n", mReporterName1);
-  mReportStackTrace1->Print(aWriter);
+  mReportStackTrace1->Print(aWriter, aLocService);
 
   W("\n Now reported by '%s' at\n", mReporterName2);
-  mReportStackTrace2->Print(aWriter);
+  mReportStackTrace2->Print(aWriter, aLocService);
 
   W("\n");
 }
 
 //---------------------------------------------------------------------------
 // Stack frame groups
 //---------------------------------------------------------------------------
 
@@ -1313,18 +1485,18 @@ public:
   // This is |const| thanks to the |mutable| fields above.
   void Add(const LiveBlockGroup& aBg) const
   {
     mNumBlocks += aBg.mNumBlocks;
     mNumBlockGroups++;
     mGroupSize.Add(aBg.mGroupSize);
   }
 
-  void Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
-             const char* aStr, const char* astr,
+  void Print(const Writer& aWriter, LocationService* aLocService,
+             uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
              size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
              size_t aTotalUsableSize) const;
 
   static int QsortCmp(const void* aA, const void* aB)
   {
     const FrameGroup* const a = *static_cast<const FrameGroup* const*>(aA);
     const FrameGroup* const b = *static_cast<const FrameGroup* const*>(aB);
 
@@ -1349,28 +1521,25 @@ public:
 };
 
 const char* const FrameGroup::kName = "frame";
 
 typedef js::HashSet<FrameGroup, FrameGroup, InfallibleAllocPolicy>
         FrameGroupTable;
 
 void
-FrameGroup::Print(const Writer& aWriter, uint32_t aM, uint32_t aN,
-                  const char* aStr, const char* astr,
+FrameGroup::Print(const Writer& aWriter, LocationService* aLocService,
+                  uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
                   size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
                   size_t aTotalUsableSize) const
 {
   (void)aCumulativeUsableSize;
 
   bool showTilde = mGroupSize.IsSampled();
 
-  nsCodeAddressDetails details;
-  PcInfo(mPc, &details);
-
   W("%s: %s block%s and %s block group%s in frame group %s of %s\n",
     aStr,
     Show(mNumBlocks, gBuf1, kBufLen, showTilde), Plural(mNumBlocks),
     Show(mNumBlockGroups, gBuf2, kBufLen, showTilde), Plural(mNumBlockGroups),
     Show(aM, gBuf3, kBufLen),
     Show(aN, gBuf4, kBufLen));
 
   W(" %s bytes (%s requested / %s slop)\n",
@@ -1379,18 +1548,18 @@ FrameGroup::Print(const Writer& aWriter,
     Show(mGroupSize.Slop(),   gBuf3, kBufLen, showTilde));
 
   W(" %4.2f%% of the heap;  %4.2f%% of %s\n",
     Percent(mGroupSize.Usable(), aTotalUsableSize),
     Percent(mGroupSize.Usable(), aCategoryUsableSize),
     astr);
 
   W(" PC is\n");
-  W("   %s[%s +0x%X] %p\n\n", details.function, details.library,
-    details.loffset, mPc);
+  aLocService->WriteLocation(aWriter, mPc);
+  W("\n");
 }
 
 //---------------------------------------------------------------------------
 // DMD start-up
 //---------------------------------------------------------------------------
 
 static void RunTestMode(FILE* fp);
 static void RunStressMode(FILE* fp);
@@ -1675,17 +1844,18 @@ ReportOnAlloc(const void* aPtr, const ch
 
 //---------------------------------------------------------------------------
 // DMD output
 //---------------------------------------------------------------------------
 
 // This works for LiveBlockGroups, DoubleReportBlockGroups and FrameGroups.
 template <class TGroup>
 static void
-PrintSortedGroups(const Writer& aWriter, const char* aStr, const char* astr,
+PrintSortedGroups(const Writer& aWriter, LocationService* aLocService,
+                  const char* aStr, const char* astr,
                   const js::HashSet<TGroup, TGroup, InfallibleAllocPolicy>& aTGroupTable,
                   size_t aCategoryUsableSize, size_t aTotalUsableSize)
 {
   const char* name = TGroup::kName;
   StatusMsg("  creating and sorting %s %s group array...\n", astr, name);
 
   // Convert the table into a sorted array.
   js::Vector<const TGroup*, 0, InfallibleAllocPolicy> tgArray;
@@ -1713,46 +1883,47 @@ PrintSortedGroups(const Writer& aWriter,
   uint32_t numTGroups = tgArray.length();
 
   StatusMsg("  printing %s %s group array...\n", astr, name);
   size_t cumulativeUsableSize = 0;
   for (uint32_t i = 0; i < numTGroups; i++) {
     const TGroup* tg = tgArray[i];
     cumulativeUsableSize += tg->GroupSize().Usable();
     if (i < MaxTGroups) {
-      tg->Print(aWriter, i+1, numTGroups, aStr, astr, aCategoryUsableSize,
-                cumulativeUsableSize, aTotalUsableSize);
+      tg->Print(aWriter, aLocService, i+1, numTGroups, aStr, astr,
+                aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize);
     } else if (i == MaxTGroups) {
       W("%s: stopping after %s %s groups\n\n", aStr,
         Show(MaxTGroups, gBuf1, kBufLen), name);
     }
   }
 
   MOZ_ASSERT(aCategoryUsableSize == kNoSize ||
              aCategoryUsableSize == cumulativeUsableSize);
 }
 
 static void
 PrintSortedBlockAndFrameGroups(const Writer& aWriter,
+                               LocationService* aLocService,
                                const char* aStr, const char* astr,
                                const LiveBlockGroupTable& aLiveBlockGroupTable,
                                size_t aCategoryUsableSize,
                                size_t aTotalUsableSize)
 {
-  PrintSortedGroups(aWriter, aStr, astr, aLiveBlockGroupTable,
+  PrintSortedGroups(aWriter, aLocService, aStr, astr, aLiveBlockGroupTable,
                     aCategoryUsableSize, aTotalUsableSize);
 
   // Frame groups are totally dependent on vagaries of stack traces, so we
   // can't show them in test mode.
   if (gMode == Test) {
     return;
   }
 
   FrameGroupTable frameGroupTable;
-  frameGroupTable.init(2048);
+  (void)frameGroupTable.init(2048);
   for (LiveBlockGroupTable::Range r = aLiveBlockGroupTable.all();
        !r.empty();
        r.popFront()) {
     const LiveBlockGroup& bg = r.front();
     const StackTrace* st = bg.mAllocStackTrace;
 
     // A single PC can appear multiple times in a stack trace.  We ignore
     // duplicates by first sorting and then ignoring adjacent duplicates.
@@ -1769,17 +1940,17 @@ PrintSortedBlockAndFrameGroups(const Wri
       FrameGroupTable::AddPtr p = frameGroupTable.lookupForAdd(pc);
       if (!p) {
         FrameGroup fg(pc);
         (void)frameGroupTable.add(p, fg);
       }
       p->Add(bg);
     }
   }
-  PrintSortedGroups(aWriter, aStr, astr, frameGroupTable, kNoSize,
+  PrintSortedGroups(aWriter, aLocService, aStr, astr, frameGroupTable, kNoSize,
                     aTotalUsableSize);
 }
 
 // Note that, unlike most SizeOf* functions, this function does not take a
 // |nsMallocSizeOfFun| argument.  That's because those arguments are primarily
 // to aid DMD track heap blocks... but DMD deliberately doesn't track heap
 // blocks it allocated for itself!
 MOZ_EXPORT void
@@ -1798,29 +1969,29 @@ SizeOf(Sizes* aSizes)
 
   aSizes->mLiveBlockTable = gLiveBlockTable->sizeOfIncludingThis(MallocSizeOf);
 
   aSizes->mDoubleReportTable =
     gDoubleReportBlockGroupTable->sizeOfIncludingThis(MallocSizeOf);
 }
 
 static void
-ClearState()
+ClearGlobalState()
 {
   // Unreport all blocks, except those that were reported on allocation,
   // because they need to keep their reported marking.
   for (LiveBlockTable::Range r = gLiveBlockTable->all();
        !r.empty();
        r.popFront()) {
     r.front().UnreportIfNotReportedOnAlloc();
   }
 
   // Clear errors.
   gDoubleReportBlockGroupTable->finish();
-  gDoubleReportBlockGroupTable->init();
+  (void)gDoubleReportBlockGroupTable->init();
 }
 
 MOZ_EXPORT void
 Dump(Writer aWriter)
 {
   if (!gIsDMDRunning) {
     const char* msg = "cannot Dump();  DMD was not enabled at startup\n";
     StatusMsg("%s", msg);
@@ -1868,24 +2039,29 @@ Dump(Writer aWriter)
     anyBlocksSampled = anyBlocksSampled || b.IsSampled();
   }
   size_t totalUsableSize = unreportedUsableSize + reportedUsableSize;
 
   WriteTitle("Invocation\n");
   W("$DMD = '%s'\n", gDMDEnvVar);
   W("Sample-below size = %lld\n\n", (long long)(gSampleBelowSize));
 
-  PrintSortedGroups(aWriter, "Double-reported", "double-reported",
+  // Allocate this on the heap instead of the stack because it's fairly large.
+  LocationService* locService = InfallibleAllocPolicy::new_<LocationService>();
+
+  PrintSortedGroups(aWriter, locService, "Double-reported", "double-reported",
                     *gDoubleReportBlockGroupTable, kNoSize, kNoSize);
 
-  PrintSortedBlockAndFrameGroups(aWriter, "Unreported", "unreported",
+  PrintSortedBlockAndFrameGroups(aWriter, locService,
+                                 "Unreported", "unreported",
                                  unreportedLiveBlockGroupTable,
                                  unreportedUsableSize, totalUsableSize);
 
-  PrintSortedBlockAndFrameGroups(aWriter, "Reported", "reported",
+  PrintSortedBlockAndFrameGroups(aWriter, locService,
+                                 "Reported", "reported",
                                  reportedLiveBlockGroupTable,
                                  reportedUsableSize, totalUsableSize);
 
   bool showTilde = anyBlocksSampled;
   WriteTitle("Summary\n");
   W("Total:      %s bytes\n",
     Show(totalUsableSize, gBuf1, kBufLen, showTilde));
   W("Reported:   %s bytes (%5.2f%%)\n",
@@ -1914,41 +2090,63 @@ Dump(Writer aWriter)
       Show(gStackTraceTable->capacity(), gBuf2, kBufLen),
       Show(gStackTraceTable->count(),    gBuf3, kBufLen));
 
     W("  Live block table:    %10s bytes (%s entries, %s used)\n",
       Show(sizes.mLiveBlockTable,       gBuf1, kBufLen),
       Show(gLiveBlockTable->capacity(), gBuf2, kBufLen),
       Show(gLiveBlockTable->count(),    gBuf3, kBufLen));
 
-    W("\nData structures that are cleared after Dump() ends:\n");
+    W("\nData structures that are partly cleared after Dump() ends:\n");
 
     W("  Double-report table: %10s bytes (%s entries, %s used)\n",
       Show(sizes.mDoubleReportTable,                 gBuf1, kBufLen),
       Show(gDoubleReportBlockGroupTable->capacity(), gBuf2, kBufLen),
       Show(gDoubleReportBlockGroupTable->count(),    gBuf3, kBufLen));
 
+    W("\nData structures that are destroyed after Dump() ends:\n");
+
     size_t unreportedSize =
       unreportedLiveBlockGroupTable.sizeOfIncludingThis(MallocSizeOf);
     W("  Unreported table:    %10s bytes (%s entries, %s used)\n",
       Show(unreportedSize,                           gBuf1, kBufLen),
       Show(unreportedLiveBlockGroupTable.capacity(), gBuf2, kBufLen),
       Show(unreportedLiveBlockGroupTable.count(),    gBuf3, kBufLen));
 
     size_t reportedSize =
       reportedLiveBlockGroupTable.sizeOfIncludingThis(MallocSizeOf);
     W("  Reported table:      %10s bytes (%s entries, %s used)\n",
       Show(reportedSize,                           gBuf1, kBufLen),
       Show(reportedLiveBlockGroupTable.capacity(), gBuf2, kBufLen),
       Show(reportedLiveBlockGroupTable.count(),    gBuf3, kBufLen));
 
+    W("  Location service:    %10s bytes\n",
+      Show(locService->SizeOfIncludingThis(), gBuf1, kBufLen));
+
+    W("\nCounts:\n");
+
+    size_t hits   = locService->NumCacheHits();
+    size_t misses = locService->NumCacheMisses();
+    size_t requests = hits + misses;
+    W("  Location service:    %10s requests\n",
+      Show(requests, gBuf1, kBufLen));
+
+    size_t count    = locService->CacheCount();
+    size_t capacity = locService->CacheCapacity();
+    double hitRate   = 100 * double(hits) / requests;
+    double occupancy = 100 * double(count) / capacity;
+    W("  Location service cache:  %4.1f%% hit rate, %.1f%% occupancy at end\n",
+      hitRate, occupancy);
+
     W("\n");
   }
 
-  ClearState();
+  InfallibleAllocPolicy::delete_(locService);
+
+  ClearGlobalState();
 
   StatusMsg("}\n");
 }
 
 //---------------------------------------------------------------------------
 // Testing
 //---------------------------------------------------------------------------