Bug 1678416 - Move more state to CCGCScheduler r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D97692
This commit is contained in:
Steve Fink 2020-12-04 02:40:13 +00:00
Родитель cd97876818
Коммит 393195efb8
2 изменённых файлов: 95 добавлений и 60 удалений

Просмотреть файл

@ -9,6 +9,8 @@
#include "nsCycleCollector.h"
#include "nsJSEnvironment.h"
using mozilla::TimeDuration;
static const mozilla::TimeDuration kOneMinute =
mozilla::TimeDuration::FromSeconds(60.0f);
@ -99,9 +101,19 @@ class CCGCScheduler {
return mCleanupsSinceLastGC < aN;
}
bool NeedsFullGC() const { return mNeedsFullGC; }
// State modification
void SetNeedsFullCC() { mNeedsFullCC = true; }
void SetNeedsFullGC(bool aNeedGC = true) { mNeedsFullGC = aNeedGC; }
// Ensure that the current runner does a cycle collection, and trigger a GC
// after it finishes.
void EnsureCCThenGC() {
MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
mNeedsFullCC = true;
mNeedsGCAfterCC = true;
}
void NoteGCBegin() {
// Treat all GC as incremental here; non-incremental GC will just appear to
@ -111,8 +123,14 @@ class CCGCScheduler {
void NoteGCEnd() {
mCCBlockStart = TimeStamp();
mCleanupsSinceLastGC = 0;
mInIncrementalGC = false;
mNeedsFullCC = true;
mHasRunGC = true;
mCleanupsSinceLastGC = 0;
mCCollectedWaitingForGC = 0;
mCCollectedZonesWaitingForGC = 0;
mLikelyShortLivingObjectsNeedingGC = 0;
}
// When we decide to do a cycle collection but we're in the middle of an
@ -142,9 +160,23 @@ class CCGCScheduler {
return aSuspectedBeforeForgetSkippable - suspected;
}
// After collecting cycles, record the results that are used in scheduling
// decisions.
void NoteCycleCollected(const CycleCollectorResults& aResults) {
mCCollectedWaitingForGC += aResults.mFreedGCed;
mCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
}
// This is invoked when the whole process of collection is done -- i.e., CC
// preparation (eg ForgetSkippables), the CC itself, and the optional
// followup GC. There really ought to be a separate name for the overall CC
// as opposed to the actual cycle collection portion.
void NoteCCEnd(TimeStamp aWhen) {
mLastCCEndTime = aWhen;
mNeedsFullCC = false;
// The GC for this CC has already been requested.
mNeedsGCAfterCC = false;
}
// The CC was abandoned without running a slice, so we only did forget
@ -153,6 +185,8 @@ class CCGCScheduler {
mLastForgetSkippableCycleEndTime = TimeStamp::Now();
}
void Shutdown() { mDidShutdown = true; }
// Scheduling
TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline) const {
@ -240,6 +274,10 @@ class CCGCScheduler {
}
bool ShouldScheduleCC() const {
if (!mHasRunGC) {
return false;
}
TimeStamp now = TimeStamp::Now();
// Don't run consecutive CCs too often.
@ -262,6 +300,13 @@ class CCGCScheduler {
return IsCCNeeded(nsCycleCollector_suspectedCount(), now);
}
// If we collected a substantial amount of cycles, poke the GC since more
// objects might be unreachable now.
bool NeedsGCAfterCC() const {
return mCCollectedWaitingForGC > 250 || mCCollectedZonesWaitingForGC > 0 ||
mLikelyShortLivingObjectsNeedingGC > 2500 || mNeedsGCAfterCC;
}
bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) {
int32_t numEarlyTimerFires =
std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1);
@ -318,6 +363,10 @@ class CCGCScheduler {
void DeactivateCCRunner() { mCCRunnerState = CCRunnerState::Inactive; }
CCRunnerStep GetNextCCRunnerAction(TimeStamp aDeadline, uint32_t aSuspected) {
if (mDidShutdown) {
return {CCRunnerAction::StopRunning, Yield};
}
if (mCCRunnerState == CCRunnerState::Inactive) {
// When we cancel a cycle, there may have been a final ForgetSkippable.
return {CCRunnerAction::StopRunning, Yield};
@ -483,8 +532,8 @@ class CCGCScheduler {
(endPoint - mForgetSkippableFrequencyStartTime).ToSeconds() / 60;
uint32_t frequencyPerMinute =
uint32_t(mForgetSkippableCounter / duration);
Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
frequencyPerMinute);
mozilla::Telemetry::Accumulate(
mozilla::Telemetry::FORGET_SKIPPABLE_FREQUENCY, frequencyPerMinute);
mForgetSkippableCounter = 0;
mForgetSkippableFrequencyStartTime = aStartTimeStamp;
}
@ -504,6 +553,9 @@ class CCGCScheduler {
// When the CC started actually waiting for the GC to finish. This will be
// set to non-null at a later time than mCCLockedOut.
TimeStamp mCCBlockStart;
bool mDidShutdown = false;
TimeStamp mLastForgetSkippableEndTime;
uint32_t mForgetSkippableCounter = 0;
TimeStamp mForgetSkippableFrequencyStartTime;
@ -513,11 +565,23 @@ class CCGCScheduler {
CCRunnerState mCCRunnerState = CCRunnerState::Inactive;
int32_t mCCRunnerEarlyFireCount = 0;
TimeDuration mCCDelay = kCCDelay;
// Prevent the very first CC from running before we have GC'd and set the
// gray bits.
bool mHasRunGC = false;
bool mNeedsFullCC = false;
bool mNeedsFullGC = true;
bool mNeedsGCAfterCC = false;
uint32_t mPreviousSuspectedCount = 0;
uint32_t mCleanupsSinceLastGC = UINT32_MAX;
public:
uint32_t mCCollectedWaitingForGC = 0;
uint32_t mCCollectedZonesWaitingForGC = 0;
uint32_t mLikelyShortLivingObjectsNeedingGC = 0;
// Configuration parameters
TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5);

Просмотреть файл

@ -102,19 +102,11 @@ static TimeStamp sCurrentGCStartTime;
static JS::GCSliceCallback sPrevGCSliceCallback;
static bool sHasRunGC;
static uint32_t sCCollectedWaitingForGC;
static uint32_t sCCollectedZonesWaitingForGC;
static uint32_t sLikelyShortLivingObjectsNeedingGC;
static bool sNeedsFullGC = false;
static bool sNeedsGCAfterCC = false;
static bool sIncrementalCC = false;
static TimeStamp sFirstCollectionTime;
static bool sIsInitialized;
static bool sDidShutdown;
static bool sShuttingDown;
// nsJSEnvironmentObserver observes the user-interaction-inactive notifications
@ -298,13 +290,6 @@ static void KillTimers() {
nsJSContext::KillInterSliceGCRunner();
}
// If we collected a substantial amount of cycles, poke the GC since more
// objects might be unreachable now.
static bool NeedsGCAfterCC() {
return sCCollectedWaitingForGC > 250 || sCCollectedZonesWaitingForGC > 0 ||
sLikelyShortLivingObjectsNeedingGC > 2500 || sNeedsGCAfterCC;
}
class nsJSEnvironmentObserver final : public nsIObserver {
~nsJSEnvironmentObserver() = default;
@ -337,7 +322,7 @@ nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
nsJSContext::NonIncrementalGC,
nsJSContext::ShrinkingGC);
nsJSContext::CycleCollectNow();
if (NeedsGCAfterCC()) {
if (sScheduler.NeedsGCAfterCC()) {
nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
nsJSContext::NonIncrementalGC,
nsJSContext::ShrinkingGC);
@ -1113,10 +1098,10 @@ void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
if (aIncremental == NonIncrementalGC ||
aReason == JS::GCReason::FULL_GC_TIMER) {
sNeedsFullGC = true;
sScheduler.SetNeedsFullGC();
}
if (sNeedsFullGC) {
if (sScheduler.NeedsFullGC()) {
JS::PrepareForFullGC(cx);
}
@ -1330,10 +1315,11 @@ void CycleCollectorStats::MaybeLogStats(const CycleCollectorResults& aResults,
ProcessNameForCollectorLog(), getpid(), mMaxSliceTime.ToMilliseconds(),
mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices, mSuspected,
aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
aResults.mFreedRefCounted, aResults.mFreedGCed, sCCollectedWaitingForGC,
sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
gcMsg.get(), mForgetSkippableBeforeCC,
mMinForgetSkippableTime.ToMilliseconds(),
aResults.mFreedRefCounted, aResults.mFreedGCed,
sScheduler.mCCollectedWaitingForGC,
sScheduler.mCCollectedZonesWaitingForGC,
sScheduler.mLikelyShortLivingObjectsNeedingGC, gcMsg.get(),
mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
mMaxForgetSkippableTime.ToMilliseconds(),
mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
mTotalForgetSkippableTime.ToMilliseconds(),
@ -1390,10 +1376,11 @@ void CycleCollectorStats::MaybeNotifyStats(
mMaxSliceTime.ToMilliseconds(), mTotalSliceTime.ToMilliseconds(),
mMaxGCDuration.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(),
mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed,
aResults.mFreedRefCounted, aResults.mFreedGCed, sCCollectedWaitingForGC,
sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
aResults.mForcedGC, mForgetSkippableBeforeCC,
mMinForgetSkippableTime.ToMilliseconds(),
aResults.mFreedRefCounted, aResults.mFreedGCed,
sScheduler.mCCollectedWaitingForGC,
sScheduler.mCCollectedZonesWaitingForGC,
sScheduler.mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC,
mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
mMaxForgetSkippableTime.ToMilliseconds(),
mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
mTotalForgetSkippableTime.ToMilliseconds(), mRemovedPurples);
@ -1485,7 +1472,7 @@ uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() {
}
static bool ICCRunnerFired(TimeStamp aDeadline) {
if (sDidShutdown) {
if (sScheduler.mDidShutdown) {
return false;
}
@ -1551,14 +1538,12 @@ void nsJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
// we previously called PrepareForCycleCollectionSlice(). During shutdown
// CCs, this won't happen.
sCCStats.AfterCycleCollectionSlice();
sCCollectedWaitingForGC += aResults.mFreedGCed;
sCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
sScheduler.NoteCycleCollected(aResults);
TimeStamp endCCTimeStamp = TimeStamp::Now();
TimeDuration ccNowDuration = TimeBetween(sCCStats.mBeginTime, endCCTimeStamp);
if (NeedsGCAfterCC()) {
if (sScheduler.NeedsGCAfterCC()) {
MOZ_ASSERT(StaticPrefs::javascript_options_gc_delay() >
kMaxICCDuration.ToMilliseconds(),
"A max duration ICC shouldn't reduce GC delay to 0");
@ -1580,7 +1565,6 @@ void nsJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
// Update global state to indicate we have just run a cycle collection.
sScheduler.NoteCCEnd(endCCTimeStamp);
sNeedsGCAfterCC = false;
sCCStats.Clear();
}
@ -1658,7 +1642,7 @@ void ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure) {
}
static bool CCRunnerFired(TimeStamp aDeadline) {
if (sDidShutdown) {
if (sScheduler.mDidShutdown) {
return false;
}
@ -1738,7 +1722,7 @@ void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason,
if (aReason == JS::GCReason::DOM_WINDOW_UTILS) {
// Force full GCs when called from reftests so that we collect dead zones
// that have not been scheduled for collection.
sNeedsFullGC = true;
sScheduler.SetNeedsFullGC();
}
GCTimerFired(nullptr, reinterpret_cast<void*>(aReason));
return;
@ -1841,7 +1825,7 @@ void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
JS::Zone* zone = JS::GetObjectZone(aObj);
CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(zone);
} else if (aReason != JS::GCReason::CC_WAITING) {
sNeedsFullGC = true;
sScheduler.SetNeedsFullGC();
}
if (sGCTimer || sInterSliceGCRunner) {
@ -1850,17 +1834,16 @@ void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
}
if (sCCRunner) {
// Make sure CC is called...
sScheduler.SetNeedsFullCC();
// and GC after it.
sNeedsGCAfterCC = true;
// Make sure CC is called regardless of the size of the purple buffer, and
// GC after it.
sScheduler.EnsureCCThenGC();
return;
}
if (sICCRunner) {
// Make sure GC is called after the current CC completes.
// No need to SetNeedsFullCC because we are currently running a CC.
sNeedsGCAfterCC = true;
sScheduler.mNeedsGCAfterCC = true;
return;
}
@ -1890,7 +1873,7 @@ void nsJSContext::PokeShrinkingGC() {
// static
void nsJSContext::MaybePokeCC() {
if (sCCRunner || sICCRunner || !sHasRunGC || sShuttingDown) {
if (sCCRunner || sICCRunner || !sScheduler.mHasRunGC || sShuttingDown) {
return;
}
@ -1993,11 +1976,6 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
// May need to kill the inter-slice GC runner
nsJSContext::KillInterSliceGCRunner();
sCCollectedWaitingForGC = 0;
sCCollectedZonesWaitingForGC = 0;
sLikelyShortLivingObjectsNeedingGC = 0;
sScheduler.SetNeedsFullCC();
sHasRunGC = true;
nsJSContext::MaybePokeCC();
if (aDesc.isZone_) {
@ -2016,7 +1994,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
}
if (!aDesc.isZone_) {
sNeedsFullGC = false;
sScheduler.SetNeedsFullGC(false);
}
Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS,
@ -2083,20 +2061,13 @@ void nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; }
void nsJSContext::LikelyShortLivingObjectCreated() {
++sLikelyShortLivingObjectsNeedingGC;
++sScheduler.mLikelyShortLivingObjectsNeedingGC;
}
void mozilla::dom::StartupJSEnvironment() {
// initialize all our statics, so that we can restart XPCOM
sGCTimer = sShrinkingGCTimer = sFullGCTimer = nullptr;
sHasRunGC = false;
sCCollectedWaitingForGC = 0;
sCCollectedZonesWaitingForGC = 0;
sLikelyShortLivingObjectsNeedingGC = 0;
sNeedsFullGC = true;
sNeedsGCAfterCC = false;
sIsInitialized = false;
sDidShutdown = false;
sShuttingDown = false;
new (&sScheduler) CCGCScheduler(); // Reset the scheduler state.
sCCStats.Init();
@ -2392,7 +2363,7 @@ void mozilla::dom::ShutdownJSEnvironment() {
KillTimers();
sShuttingDown = true;
sDidShutdown = true;
sScheduler.Shutdown();
}
AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)