зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1678416 - Move more state to CCGCScheduler r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D97692
This commit is contained in:
Родитель
68bc625380
Коммит
630f77d442
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче