Bug 1342714 - Reducing allocations in AutoStopwatch;r?froydnj,jandem draft
authorDavid Teller <dteller@mozilla.com>
Fri, 24 Mar 2017 22:25:03 +0100
changeset 552949 e650bcd94dccb19d3bef3b4af341880dcde9c4c5
parent 552691 272ce6c2572164f5f6a9fba2a980ba9ccf50770c
child 621959 192abfbe65c606987fe9003c51bdd45c22c3f8ce
push id51521
push userdteller@mozilla.com
push dateWed, 29 Mar 2017 09:26:22 +0000
reviewersfroydnj, jandem
bugs1342714
milestone55.0a1
Bug 1342714 - Reducing allocations in AutoStopwatch;r?froydnj,jandem This patch fixes two related issues. 1. The AutoStopwatch uses a stack-allocated `mozilla::Vector` to communicate with its callback during each compartment switch. This vector was designed to allow its contents to be stack-allocated but they turned out to be accidentally heap-allocated. 2. During each tick, the stopwatch fills a vector `recentGroups_`. This vector always started with minimal capacity and had to grow repeatedly as groups were added, causing repeated reallocations. This patch preallocates `recentGroups_` to have the same capacity as the previous tick. We expect that this should eventually reach a stable size that closely matches the actual needs of the process. MozReview-Commit-ID: A7e3HNdSuML
js/src/jsapi.h
js/src/vm/Stopwatch.cpp
toolkit/components/perfmonitoring/nsPerformanceStats.cpp
toolkit/components/perfmonitoring/nsPerformanceStats.h
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -6773,17 +6773,17 @@ struct JS_PUBLIC_API(PerformanceGroup) {
 
   public:
     // Compatibility with RefPtr<>
     void AddRef();
     void Release();
     uint64_t refCount_;
 };
 
-using PerformanceGroupVector = mozilla::Vector<RefPtr<js::PerformanceGroup>, 0, SystemAllocPolicy>;
+using PerformanceGroupVector = mozilla::Vector<RefPtr<js::PerformanceGroup>, 8, SystemAllocPolicy>;
 
 /**
  * Commit any Performance Monitoring data.
  *
  * Until `FlushMonitoring` has been called, all PerformanceMonitoring data is invisible
  * to the outside world and can cancelled with a call to `ResetMonitoring`.
  */
 extern JS_PUBLIC_API(bool)
--- a/js/src/vm/Stopwatch.cpp
+++ b/js/src/vm/Stopwatch.cpp
@@ -15,16 +15,17 @@
 #include <windows.h>
 #endif // defined(XP_WIN)
 
 #include "jscompartment.h"
 
 #include "gc/Zone.h"
 #include "vm/Runtime.h"
 
+
 namespace js {
 
 bool
 PerformanceMonitoring::addRecentGroup(PerformanceGroup* group)
 {
     if (group->isUsedInThisIteration())
         return true;
 
@@ -131,39 +132,51 @@ PerformanceMonitoring::start()
 //
 // Computing the number of cycles is fast and should be accurate
 // enough in practice. Alternatives (such as calling `getresources`
 // all the time or sampling from another thread) are very expensive
 // in system calls and/or battery and not necessarily more accurate.
 bool
 PerformanceMonitoring::commit()
 {
+    // Maximal initialization size, in elements for the vector of groups.
+    static const size_t MAX_GROUPS_INIT_CAPACITY = 1024;
+
 #if !defined(MOZ_HAVE_RDTSC)
     // The AutoStopwatch is only executed if `MOZ_HAVE_RDTSC`.
     return false;
 #endif // !defined(MOZ_HAVE_RDTSC)
 
     if (!isMonitoringJank_) {
         // Either we have not started monitoring or monitoring has
         // been cancelled during the iteration.
         return true;
     }
 
     if (startedAtIteration_ != iteration_) {
         // No JS code has been monitored during this iteration.
         return true;
     }
 
-    PerformanceGroupVector recentGroups;
-    recentGroups_.swap(recentGroups);
+    // The move operation is generally constant time, unless
+    // `recentGroups_.length()` is very small, in which case
+    // it's fast just because it's small.
+    PerformanceGroupVector recentGroups(Move(recentGroups_));
+    recentGroups_ = PerformanceGroupVector(); // Reconstruct after `Move`.
 
     bool success = true;
     if (stopwatchCommitCallback)
         success = stopwatchCommitCallback(iteration_, recentGroups, stopwatchCommitClosure);
 
+    // Heuristic: we expect to have roughly the same number of groups as in
+    // the previous iteration.
+    const size_t capacity = std::min(recentGroups.capacity(), MAX_GROUPS_INIT_CAPACITY);
+    success = recentGroups_.reserve(capacity)
+            && success;
+
     // Reset immediately, to make sure that we're not hit by the end
     // of a nested event loop (which would cause `commit` to be called
     // twice in succession).
     reset();
     return success;
 }
 
 uint64_t
@@ -222,17 +235,17 @@ AutoStopwatch::AutoStopwatch(JSContext* 
   , isMonitoringJank_(false)
   , isMonitoringCPOW_(false)
   , cyclesStart_(0)
   , CPOWTimeStart_(0)
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
     JSCompartment* compartment = cx_->compartment();
-    if (compartment->scheduledForDestruction)
+    if (MOZ_UNLIKELY(compartment->scheduledForDestruction))
         return;
 
     JSRuntime* runtime = cx_->runtime();
     iteration_ = runtime->performanceMonitoring().iteration();
 
     const PerformanceGroupVector* groups = compartment->performanceMonitoring.getGroups(cx);
     if (!groups) {
       // Either the embedding has not provided any performance
@@ -261,21 +274,21 @@ AutoStopwatch::AutoStopwatch(JSContext* 
 AutoStopwatch::~AutoStopwatch()
 {
     if (groups_.length() == 0) {
         // We are not in charge of monitoring anything.
         return;
     }
 
     JSCompartment* compartment = cx_->compartment();
-    if (compartment->scheduledForDestruction)
+    if (MOZ_UNLIKELY(compartment->scheduledForDestruction))
         return;
 
     JSRuntime* runtime = cx_->runtime();
-    if (iteration_ != runtime->performanceMonitoring().iteration()) {
+    if (MOZ_UNLIKELY(iteration_ != runtime->performanceMonitoring().iteration())) {
         // We have entered a nested event loop at some point.
         // Any information we may have is obsolete.
         return;
     }
 
     mozilla::Unused << exit(); // Sadly, there is nothing we can do about an error at this point.
 
     for (auto group = groups_.begin(); group < groups_.end(); group++)
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
@@ -1126,16 +1126,19 @@ nsPerformanceStatsService::GetPerformanc
                              name, addonId, windowId,
                              mProcessId, isSystem,
                              nsPerformanceGroup::GroupScope::COMPARTMENT);
   if (!out.append(group)) {
     JS_ReportOutOfMemory(cx);
     return false;
   }
 
+  // Returning a vector that is too large would cause allocations all over the
+  // place in the JS engine. We want to be sure that all data is stored inline.
+  MOZ_ASSERT(out.length() <= out.sMaxInlineStorage);
   return true;
 }
 
 /*static*/ bool
 nsPerformanceStatsService::StopwatchStartCallback(uint64_t iteration, void* closure) {
   RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
   return self->StopwatchStart(iteration);
 }
@@ -1354,18 +1357,22 @@ nsPerformanceStatsService::GetResources(
 
 #endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
 
   return NS_OK;
 }
 
 void
 nsPerformanceStatsService::NotifyJankObservers(const mozilla::Vector<uint64_t>& aPreviousJankLevels) {
-  GroupVector alerts;
-  mPendingAlerts.swap(alerts);
+
+  // The move operation is generally constant time, unless
+  // `mPendingAlerts.length()` is very small, in which case it's fast anyway.
+  GroupVector alerts(Move(mPendingAlerts));
+  mPendingAlerts = GroupVector(); // Reconstruct after `Move`.
+
   if (!mPendingAlertsCollector) {
     // We are shutting down.
     return;
   }
 
   // Find out if we have noticed any user-noticeable delay in an
   // animation recently (i.e. since the start of the execution of JS
   // code that caused this collector to start). If so, we'll mark any
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.h
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.h
@@ -14,17 +14,17 @@
 #include "nsIObserver.h"
 #include "nsPIDOMWindow.h"
 
 #include "nsIPerformanceStats.h"
 
 class nsPerformanceGroup;
 class nsPerformanceGroupDetails;
 
-typedef mozilla::Vector<RefPtr<nsPerformanceGroup>> GroupVector;
+typedef mozilla::Vector<RefPtr<nsPerformanceGroup>, 8> GroupVector;
 
 /**
  * A data structure for registering observers interested in
  * performance alerts.
  *
  * Each performance group owns a single instance of this class.
  * Additionally, the service owns instances designed to observe the
  * performance alerts in all add-ons (respectively webpages).