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
--- 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).