зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1703443 - pt 15. Move more CCGCScheduler implementation into the .cpp file r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D118698
This commit is contained in:
Родитель
ae50acaba7
Коммит
d38a251c59
|
@ -357,4 +357,322 @@ void CCGCScheduler::KillAllTimersAndRunners() {
|
|||
KillGCRunner();
|
||||
}
|
||||
|
||||
js::SliceBudget CCGCScheduler::ComputeCCSliceBudget(
|
||||
TimeStamp aDeadline, TimeStamp aCCBeginTime, TimeStamp aPrevSliceEndTime,
|
||||
TimeStamp aNow, bool* aPreferShorterSlices) const {
|
||||
*aPreferShorterSlices =
|
||||
aDeadline.IsNull() || (aDeadline - aNow) < kICCSliceBudget;
|
||||
|
||||
TimeDuration baseBudget =
|
||||
aDeadline.IsNull() ? kICCSliceBudget : aDeadline - aNow;
|
||||
|
||||
if (aCCBeginTime.IsNull()) {
|
||||
// If no CC is in progress, use the standard slice time.
|
||||
return js::SliceBudget(js::TimeBudget(baseBudget),
|
||||
kNumCCNodesBetweenTimeChecks);
|
||||
}
|
||||
|
||||
// Only run a limited slice if we're within the max running time.
|
||||
MOZ_ASSERT(aNow >= aCCBeginTime);
|
||||
TimeDuration runningTime = aNow - aCCBeginTime;
|
||||
if (runningTime >= kMaxICCDuration) {
|
||||
return js::SliceBudget::unlimited();
|
||||
}
|
||||
|
||||
const TimeDuration maxSlice =
|
||||
TimeDuration::FromMilliseconds(MainThreadIdlePeriod::GetLongIdlePeriod());
|
||||
|
||||
// Try to make up for a delay in running this slice.
|
||||
MOZ_ASSERT(aNow >= aPrevSliceEndTime);
|
||||
double sliceDelayMultiplier =
|
||||
(aNow - aPrevSliceEndTime) / kICCIntersliceDelay;
|
||||
TimeDuration delaySliceBudget =
|
||||
std::min(baseBudget.MultDouble(sliceDelayMultiplier), maxSlice);
|
||||
|
||||
// Increase slice budgets up to |maxSlice| as we approach
|
||||
// half way through the ICC, to avoid large sync CCs.
|
||||
double percentToHalfDone =
|
||||
std::min(2.0 * (runningTime / kMaxICCDuration), 1.0);
|
||||
TimeDuration laterSliceBudget = maxSlice.MultDouble(percentToHalfDone);
|
||||
|
||||
// Note: We may have already overshot the deadline, in which case
|
||||
// baseBudget will be negative and we will end up returning
|
||||
// laterSliceBudget.
|
||||
return js::SliceBudget(js::TimeBudget(std::max(
|
||||
{delaySliceBudget, laterSliceBudget, baseBudget})),
|
||||
kNumCCNodesBetweenTimeChecks);
|
||||
}
|
||||
|
||||
TimeDuration CCGCScheduler::ComputeInterSliceGCBudget(TimeStamp aDeadline,
|
||||
TimeStamp aNow) const {
|
||||
// We use longer budgets when the CC has been locked out but the CC has
|
||||
// tried to run since that means we may have a significant amount of
|
||||
// garbage to collect and it's better to GC in several longer slices than
|
||||
// in a very long one.
|
||||
TimeDuration budget =
|
||||
aDeadline.IsNull() ? mActiveIntersliceGCBudget * 2 : aDeadline - aNow;
|
||||
if (!mCCBlockStart) {
|
||||
return budget;
|
||||
}
|
||||
|
||||
TimeDuration blockedTime = aNow - mCCBlockStart;
|
||||
TimeDuration maxSliceGCBudget = mActiveIntersliceGCBudget * 10;
|
||||
double percentOfBlockedTime =
|
||||
std::min(blockedTime / kMaxCCLockedoutTime, 1.0);
|
||||
return std::max(budget, maxSliceGCBudget.MultDouble(percentOfBlockedTime));
|
||||
}
|
||||
|
||||
bool CCGCScheduler::ShouldScheduleCC(TimeStamp aNow,
|
||||
uint32_t aSuspectedCCObjects) const {
|
||||
if (!mHasRunGC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't run consecutive CCs too often.
|
||||
if (mCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) {
|
||||
if (aNow - mLastCCEndTime < kCCDelay) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If GC hasn't run recently and forget skippable only cycle was run,
|
||||
// don't start a new cycle too soon.
|
||||
if ((mCleanupsSinceLastGC > kMajorForgetSkippableCalls) &&
|
||||
!mLastForgetSkippableCycleEndTime.IsNull()) {
|
||||
if (aNow - mLastForgetSkippableCycleEndTime <
|
||||
kTimeBetweenForgetSkippableCycles) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return IsCCNeeded(aNow, aSuspectedCCObjects);
|
||||
}
|
||||
|
||||
CCRunnerStep CCGCScheduler::AdvanceCCRunner(TimeStamp aDeadline, TimeStamp aNow,
|
||||
uint32_t aSuspectedCCObjects) {
|
||||
struct StateDescriptor {
|
||||
// When in this state, should we first check to see if we still have
|
||||
// enough reason to CC?
|
||||
bool mCanAbortCC;
|
||||
|
||||
// If we do decide to abort the CC, should we still try to forget
|
||||
// skippables one more time?
|
||||
bool mTryFinalForgetSkippable;
|
||||
};
|
||||
|
||||
// The state descriptors for Inactive and Canceled will never actually be
|
||||
// used. We will never call this function while Inactive, and Canceled is
|
||||
// handled specially at the beginning.
|
||||
constexpr StateDescriptor stateDescriptors[] = {
|
||||
{false, false}, /* CCRunnerState::Inactive */
|
||||
{false, false}, /* CCRunnerState::ReducePurple */
|
||||
{true, true}, /* CCRunnerState::CleanupChildless */
|
||||
{true, false}, /* CCRunnerState::CleanupContentUnbinder */
|
||||
{false, false}, /* CCRunnerState::CleanupDeferred */
|
||||
{false, false}, /* CCRunnerState::StartCycleCollection */
|
||||
{false, false}, /* CCRunnerState::CycleCollecting */
|
||||
{false, false}}; /* CCRunnerState::Canceled */
|
||||
static_assert(
|
||||
ArrayLength(stateDescriptors) == size_t(CCRunnerState::NumStates),
|
||||
"need one state descriptor per state");
|
||||
const StateDescriptor& desc = stateDescriptors[int(mCCRunnerState)];
|
||||
|
||||
// Make sure we initialized the state machine.
|
||||
MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
|
||||
|
||||
if (mDidShutdown) {
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
if (mCCRunnerState == CCRunnerState::Canceled) {
|
||||
// When we cancel a cycle, there may have been a final ForgetSkippable.
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
if (InIncrementalGC()) {
|
||||
if (mCCBlockStart.IsNull()) {
|
||||
BlockCC(aNow);
|
||||
|
||||
// If we have reached the CycleCollecting state, then ignore CC timer
|
||||
// fires while incremental GC is running. (Running ICC during an IGC
|
||||
// would cause us to synchronously finish the GC, which is bad.)
|
||||
//
|
||||
// If we have not yet started cycle collecting, then reset our state so
|
||||
// that we run forgetSkippable often enough before CC. Because of reduced
|
||||
// mCCDelay, forgetSkippable will be called just a few times.
|
||||
//
|
||||
// The kMaxCCLockedoutTime limit guarantees that we end up calling
|
||||
// forgetSkippable and CycleCollectNow eventually.
|
||||
|
||||
if (mCCRunnerState != CCRunnerState::CycleCollecting) {
|
||||
mCCRunnerState = CCRunnerState::ReducePurple;
|
||||
mCCRunnerEarlyFireCount = 0;
|
||||
mCCDelay = kCCDelay / int64_t(3);
|
||||
}
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
if (GetCCBlockedTime(aNow) < kMaxCCLockedoutTime) {
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
// Locked out for too long, so proceed and finish the incremental GC
|
||||
// synchronously.
|
||||
}
|
||||
|
||||
// For states that aren't just continuations of previous states, check
|
||||
// whether a CC is still needed (after doing various things to reduce the
|
||||
// purple buffer).
|
||||
if (desc.mCanAbortCC && !IsCCNeeded(aNow, aSuspectedCCObjects)) {
|
||||
// If we don't pass the threshold for wanting to cycle collect, stop now
|
||||
// (after possibly doing a final ForgetSkippable).
|
||||
mCCRunnerState = CCRunnerState::Canceled;
|
||||
NoteForgetSkippableOnlyCycle(aNow);
|
||||
|
||||
// Preserve the previous code's idea of when to check whether a
|
||||
// ForgetSkippable should be fired.
|
||||
if (desc.mTryFinalForgetSkippable &&
|
||||
ShouldForgetSkippable(aSuspectedCCObjects)) {
|
||||
// The Canceled state will make us StopRunning after this action is
|
||||
// performed (see conditional at top of function).
|
||||
return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
|
||||
}
|
||||
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
switch (mCCRunnerState) {
|
||||
// ReducePurple: a GC ran (or we otherwise decided to try CC'ing). Wait
|
||||
// for some amount of time (kCCDelay, or less if incremental GC blocked
|
||||
// this CC) while firing regular ForgetSkippable actions before continuing
|
||||
// on.
|
||||
case CCRunnerState::ReducePurple:
|
||||
++mCCRunnerEarlyFireCount;
|
||||
if (IsLastEarlyCCTimer(mCCRunnerEarlyFireCount)) {
|
||||
mCCRunnerState = CCRunnerState::CleanupChildless;
|
||||
}
|
||||
|
||||
if (ShouldForgetSkippable(aSuspectedCCObjects)) {
|
||||
return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
|
||||
}
|
||||
|
||||
if (aDeadline.IsNull()) {
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
// If we're called during idle time, try to find some work to do by
|
||||
// advancing to the next state, effectively bypassing some possible forget
|
||||
// skippable calls.
|
||||
mCCRunnerState = CCRunnerState::CleanupChildless;
|
||||
|
||||
// Continue on to CleanupChildless, but only after checking IsCCNeeded
|
||||
// again.
|
||||
return {CCRunnerAction::None, Continue};
|
||||
|
||||
// CleanupChildless: do a stronger ForgetSkippable that removes nodes with
|
||||
// no children in the cycle collector graph. This state is split into 3
|
||||
// parts; the other Cleanup* actions will happen within the same callback
|
||||
// (unless the ForgetSkippable shrinks the purple buffer enough for the CC
|
||||
// to be skipped entirely.)
|
||||
case CCRunnerState::CleanupChildless:
|
||||
mCCRunnerState = CCRunnerState::CleanupContentUnbinder;
|
||||
return {CCRunnerAction::ForgetSkippable, Yield, RemoveChildless};
|
||||
|
||||
// CleanupContentUnbinder: continuing cleanup, clear out the content
|
||||
// unbinder.
|
||||
case CCRunnerState::CleanupContentUnbinder:
|
||||
if (aDeadline.IsNull()) {
|
||||
// Non-idle (waiting) callbacks skip the rest of the cleanup, but still
|
||||
// wait for another fire before the actual CC.
|
||||
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
// Running in an idle callback.
|
||||
|
||||
// The deadline passed, so go straight to CC in the next slice.
|
||||
if (aNow >= aDeadline) {
|
||||
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
mCCRunnerState = CCRunnerState::CleanupDeferred;
|
||||
return {CCRunnerAction::CleanupContentUnbinder, Continue};
|
||||
|
||||
// CleanupDeferred: continuing cleanup, do deferred deletion.
|
||||
case CCRunnerState::CleanupDeferred:
|
||||
MOZ_ASSERT(!aDeadline.IsNull(),
|
||||
"Should only be in CleanupDeferred state when idle");
|
||||
|
||||
// Our efforts to avoid a CC have failed. Let the timer fire once more
|
||||
// to trigger a CC.
|
||||
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
||||
if (aNow >= aDeadline) {
|
||||
// The deadline passed, go straight to CC in the next slice.
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
return {CCRunnerAction::CleanupDeferred, Yield};
|
||||
|
||||
// StartCycleCollection: start actually doing cycle collection slices.
|
||||
case CCRunnerState::StartCycleCollection:
|
||||
// We are in the final timer fire and still meet the conditions for
|
||||
// triggering a CC. Let RunCycleCollectorSlice finish the current IGC if
|
||||
// any, because that will allow us to include the GC time in the CC pause.
|
||||
mCCRunnerState = CCRunnerState::CycleCollecting;
|
||||
[[fallthrough]];
|
||||
|
||||
// CycleCollecting: continue running slices until done.
|
||||
case CCRunnerState::CycleCollecting:
|
||||
return {CCRunnerAction::CycleCollect, Yield};
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Unexpected CCRunner state");
|
||||
};
|
||||
}
|
||||
|
||||
GCRunnerStep CCGCScheduler::GetNextGCRunnerAction(TimeStamp aDeadline) {
|
||||
MOZ_ASSERT(mMajorGCReason != JS::GCReason::NO_REASON);
|
||||
|
||||
if (InIncrementalGC()) {
|
||||
return {GCRunnerAction::GCSlice, mMajorGCReason};
|
||||
}
|
||||
|
||||
if (mReadyForMajorGC) {
|
||||
return {GCRunnerAction::StartMajorGC, mMajorGCReason};
|
||||
}
|
||||
|
||||
return {GCRunnerAction::WaitToMajorGC, mMajorGCReason};
|
||||
}
|
||||
|
||||
js::SliceBudget CCGCScheduler::ComputeForgetSkippableBudget(
|
||||
TimeStamp aStartTimeStamp, TimeStamp aDeadline) {
|
||||
if (mForgetSkippableFrequencyStartTime.IsNull()) {
|
||||
mForgetSkippableFrequencyStartTime = aStartTimeStamp;
|
||||
} else if (aStartTimeStamp - mForgetSkippableFrequencyStartTime >
|
||||
kOneMinute) {
|
||||
TimeStamp startPlusMinute = mForgetSkippableFrequencyStartTime + kOneMinute;
|
||||
|
||||
// If we had forget skippables only at the beginning of the interval, we
|
||||
// still want to use the whole time, minute or more, for frequency
|
||||
// calculation. mLastForgetSkippableEndTime is needed if forget skippable
|
||||
// takes enough time to push the interval to be over a minute.
|
||||
TimeStamp endPoint = std::max(startPlusMinute, mLastForgetSkippableEndTime);
|
||||
|
||||
// Duration in minutes.
|
||||
double duration =
|
||||
(endPoint - mForgetSkippableFrequencyStartTime).ToSeconds() / 60;
|
||||
uint32_t frequencyPerMinute = uint32_t(mForgetSkippableCounter / duration);
|
||||
Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
|
||||
frequencyPerMinute);
|
||||
mForgetSkippableCounter = 0;
|
||||
mForgetSkippableFrequencyStartTime = aStartTimeStamp;
|
||||
}
|
||||
++mForgetSkippableCounter;
|
||||
|
||||
TimeDuration budgetTime =
|
||||
aDeadline ? (aDeadline - aStartTimeStamp) : kForgetSkippableSliceDuration;
|
||||
return js::SliceBudget(budgetTime);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -300,14 +300,14 @@ class CCGCScheduler {
|
|||
// Return a budget along with a boolean saying whether to prefer to run short
|
||||
// slices and stop rather than continuing to the next phase of cycle
|
||||
// collection.
|
||||
inline js::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline,
|
||||
TimeStamp aCCBeginTime,
|
||||
TimeStamp aPrevSliceEndTime,
|
||||
TimeStamp aNow,
|
||||
bool* aPreferShorterSlices) const;
|
||||
js::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline,
|
||||
TimeStamp aCCBeginTime,
|
||||
TimeStamp aPrevSliceEndTime,
|
||||
TimeStamp aNow,
|
||||
bool* aPreferShorterSlices) const;
|
||||
|
||||
inline TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline,
|
||||
TimeStamp aNow) const;
|
||||
TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline,
|
||||
TimeStamp aNow) const;
|
||||
|
||||
bool ShouldForgetSkippable(uint32_t aSuspectedCCObjects) const {
|
||||
// Only do a forget skippable if there are more than a few new objects
|
||||
|
@ -329,8 +329,7 @@ class CCGCScheduler {
|
|||
aNow - mLastCCEndTime > kCCForced);
|
||||
}
|
||||
|
||||
inline bool ShouldScheduleCC(TimeStamp aNow,
|
||||
uint32_t aSuspectedCCObjects) const;
|
||||
bool ShouldScheduleCC(TimeStamp aNow, uint32_t aSuspectedCCObjects) const;
|
||||
|
||||
// If we collected a substantial amount of cycles, poke the GC since more
|
||||
// objects might be unreachable now.
|
||||
|
@ -383,10 +382,10 @@ class CCGCScheduler {
|
|||
|
||||
void DeactivateCCRunner() { mCCRunnerState = CCRunnerState::Inactive; }
|
||||
|
||||
inline GCRunnerStep GetNextGCRunnerAction(TimeStamp aDeadline);
|
||||
GCRunnerStep GetNextGCRunnerAction(TimeStamp aDeadline);
|
||||
|
||||
inline CCRunnerStep AdvanceCCRunner(TimeStamp aDeadline, TimeStamp aNow,
|
||||
uint32_t aSuspectedCCObjects);
|
||||
CCRunnerStep AdvanceCCRunner(TimeStamp aDeadline, TimeStamp aNow,
|
||||
uint32_t aSuspectedCCObjects);
|
||||
|
||||
// aStartTimeStamp : when the ForgetSkippable timer fired. This may be some
|
||||
// time ago, if an incremental GC needed to be finished.
|
||||
|
@ -452,322 +451,4 @@ class CCGCScheduler {
|
|||
TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5);
|
||||
};
|
||||
|
||||
js::SliceBudget CCGCScheduler::ComputeCCSliceBudget(
|
||||
TimeStamp aDeadline, TimeStamp aCCBeginTime, TimeStamp aPrevSliceEndTime,
|
||||
TimeStamp aNow, bool* aPreferShorterSlices) const {
|
||||
*aPreferShorterSlices =
|
||||
aDeadline.IsNull() || (aDeadline - aNow) < kICCSliceBudget;
|
||||
|
||||
TimeDuration baseBudget =
|
||||
aDeadline.IsNull() ? kICCSliceBudget : aDeadline - aNow;
|
||||
|
||||
if (aCCBeginTime.IsNull()) {
|
||||
// If no CC is in progress, use the standard slice time.
|
||||
return js::SliceBudget(js::TimeBudget(baseBudget),
|
||||
kNumCCNodesBetweenTimeChecks);
|
||||
}
|
||||
|
||||
// Only run a limited slice if we're within the max running time.
|
||||
MOZ_ASSERT(aNow >= aCCBeginTime);
|
||||
TimeDuration runningTime = aNow - aCCBeginTime;
|
||||
if (runningTime >= kMaxICCDuration) {
|
||||
return js::SliceBudget::unlimited();
|
||||
}
|
||||
|
||||
const TimeDuration maxSlice =
|
||||
TimeDuration::FromMilliseconds(MainThreadIdlePeriod::GetLongIdlePeriod());
|
||||
|
||||
// Try to make up for a delay in running this slice.
|
||||
MOZ_ASSERT(aNow >= aPrevSliceEndTime);
|
||||
double sliceDelayMultiplier =
|
||||
(aNow - aPrevSliceEndTime) / kICCIntersliceDelay;
|
||||
TimeDuration delaySliceBudget =
|
||||
std::min(baseBudget.MultDouble(sliceDelayMultiplier), maxSlice);
|
||||
|
||||
// Increase slice budgets up to |maxSlice| as we approach
|
||||
// half way through the ICC, to avoid large sync CCs.
|
||||
double percentToHalfDone =
|
||||
std::min(2.0 * (runningTime / kMaxICCDuration), 1.0);
|
||||
TimeDuration laterSliceBudget = maxSlice.MultDouble(percentToHalfDone);
|
||||
|
||||
// Note: We may have already overshot the deadline, in which case
|
||||
// baseBudget will be negative and we will end up returning
|
||||
// laterSliceBudget.
|
||||
return js::SliceBudget(js::TimeBudget(std::max(
|
||||
{delaySliceBudget, laterSliceBudget, baseBudget})),
|
||||
kNumCCNodesBetweenTimeChecks);
|
||||
}
|
||||
|
||||
inline TimeDuration CCGCScheduler::ComputeInterSliceGCBudget(
|
||||
TimeStamp aDeadline, TimeStamp aNow) const {
|
||||
// We use longer budgets when the CC has been locked out but the CC has
|
||||
// tried to run since that means we may have a significant amount of
|
||||
// garbage to collect and it's better to GC in several longer slices than
|
||||
// in a very long one.
|
||||
TimeDuration budget =
|
||||
aDeadline.IsNull() ? mActiveIntersliceGCBudget * 2 : aDeadline - aNow;
|
||||
if (!mCCBlockStart) {
|
||||
return budget;
|
||||
}
|
||||
|
||||
TimeDuration blockedTime = aNow - mCCBlockStart;
|
||||
TimeDuration maxSliceGCBudget = mActiveIntersliceGCBudget * 10;
|
||||
double percentOfBlockedTime =
|
||||
std::min(blockedTime / kMaxCCLockedoutTime, 1.0);
|
||||
return std::max(budget, maxSliceGCBudget.MultDouble(percentOfBlockedTime));
|
||||
}
|
||||
|
||||
bool CCGCScheduler::ShouldScheduleCC(TimeStamp aNow,
|
||||
uint32_t aSuspectedCCObjects) const {
|
||||
if (!mHasRunGC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't run consecutive CCs too often.
|
||||
if (mCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) {
|
||||
if (aNow - mLastCCEndTime < kCCDelay) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If GC hasn't run recently and forget skippable only cycle was run,
|
||||
// don't start a new cycle too soon.
|
||||
if ((mCleanupsSinceLastGC > kMajorForgetSkippableCalls) &&
|
||||
!mLastForgetSkippableCycleEndTime.IsNull()) {
|
||||
if (aNow - mLastForgetSkippableCycleEndTime <
|
||||
kTimeBetweenForgetSkippableCycles) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return IsCCNeeded(aNow, aSuspectedCCObjects);
|
||||
}
|
||||
|
||||
CCRunnerStep CCGCScheduler::AdvanceCCRunner(TimeStamp aDeadline, TimeStamp aNow,
|
||||
uint32_t aSuspectedCCObjects) {
|
||||
struct StateDescriptor {
|
||||
// When in this state, should we first check to see if we still have
|
||||
// enough reason to CC?
|
||||
bool mCanAbortCC;
|
||||
|
||||
// If we do decide to abort the CC, should we still try to forget
|
||||
// skippables one more time?
|
||||
bool mTryFinalForgetSkippable;
|
||||
};
|
||||
|
||||
// The state descriptors for Inactive and Canceled will never actually be
|
||||
// used. We will never call this function while Inactive, and Canceled is
|
||||
// handled specially at the beginning.
|
||||
constexpr StateDescriptor stateDescriptors[] = {
|
||||
{false, false}, /* CCRunnerState::Inactive */
|
||||
{false, false}, /* CCRunnerState::ReducePurple */
|
||||
{true, true}, /* CCRunnerState::CleanupChildless */
|
||||
{true, false}, /* CCRunnerState::CleanupContentUnbinder */
|
||||
{false, false}, /* CCRunnerState::CleanupDeferred */
|
||||
{false, false}, /* CCRunnerState::StartCycleCollection */
|
||||
{false, false}, /* CCRunnerState::CycleCollecting */
|
||||
{false, false}}; /* CCRunnerState::Canceled */
|
||||
static_assert(
|
||||
ArrayLength(stateDescriptors) == size_t(CCRunnerState::NumStates),
|
||||
"need one state descriptor per state");
|
||||
const StateDescriptor& desc = stateDescriptors[int(mCCRunnerState)];
|
||||
|
||||
// Make sure we initialized the state machine.
|
||||
MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
|
||||
|
||||
if (mDidShutdown) {
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
if (mCCRunnerState == CCRunnerState::Canceled) {
|
||||
// When we cancel a cycle, there may have been a final ForgetSkippable.
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
if (InIncrementalGC()) {
|
||||
if (mCCBlockStart.IsNull()) {
|
||||
BlockCC(aNow);
|
||||
|
||||
// If we have reached the CycleCollecting state, then ignore CC timer
|
||||
// fires while incremental GC is running. (Running ICC during an IGC
|
||||
// would cause us to synchronously finish the GC, which is bad.)
|
||||
//
|
||||
// If we have not yet started cycle collecting, then reset our state so
|
||||
// that we run forgetSkippable often enough before CC. Because of reduced
|
||||
// mCCDelay, forgetSkippable will be called just a few times.
|
||||
//
|
||||
// The kMaxCCLockedoutTime limit guarantees that we end up calling
|
||||
// forgetSkippable and CycleCollectNow eventually.
|
||||
|
||||
if (mCCRunnerState != CCRunnerState::CycleCollecting) {
|
||||
mCCRunnerState = CCRunnerState::ReducePurple;
|
||||
mCCRunnerEarlyFireCount = 0;
|
||||
mCCDelay = kCCDelay / int64_t(3);
|
||||
}
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
if (GetCCBlockedTime(aNow) < kMaxCCLockedoutTime) {
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
// Locked out for too long, so proceed and finish the incremental GC
|
||||
// synchronously.
|
||||
}
|
||||
|
||||
// For states that aren't just continuations of previous states, check
|
||||
// whether a CC is still needed (after doing various things to reduce the
|
||||
// purple buffer).
|
||||
if (desc.mCanAbortCC && !IsCCNeeded(aNow, aSuspectedCCObjects)) {
|
||||
// If we don't pass the threshold for wanting to cycle collect, stop now
|
||||
// (after possibly doing a final ForgetSkippable).
|
||||
mCCRunnerState = CCRunnerState::Canceled;
|
||||
NoteForgetSkippableOnlyCycle(aNow);
|
||||
|
||||
// Preserve the previous code's idea of when to check whether a
|
||||
// ForgetSkippable should be fired.
|
||||
if (desc.mTryFinalForgetSkippable &&
|
||||
ShouldForgetSkippable(aSuspectedCCObjects)) {
|
||||
// The Canceled state will make us StopRunning after this action is
|
||||
// performed (see conditional at top of function).
|
||||
return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
|
||||
}
|
||||
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
switch (mCCRunnerState) {
|
||||
// ReducePurple: a GC ran (or we otherwise decided to try CC'ing). Wait
|
||||
// for some amount of time (kCCDelay, or less if incremental GC blocked
|
||||
// this CC) while firing regular ForgetSkippable actions before continuing
|
||||
// on.
|
||||
case CCRunnerState::ReducePurple:
|
||||
++mCCRunnerEarlyFireCount;
|
||||
if (IsLastEarlyCCTimer(mCCRunnerEarlyFireCount)) {
|
||||
mCCRunnerState = CCRunnerState::CleanupChildless;
|
||||
}
|
||||
|
||||
if (ShouldForgetSkippable(aSuspectedCCObjects)) {
|
||||
return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
|
||||
}
|
||||
|
||||
if (aDeadline.IsNull()) {
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
// If we're called during idle time, try to find some work to do by
|
||||
// advancing to the next state, effectively bypassing some possible forget
|
||||
// skippable calls.
|
||||
mCCRunnerState = CCRunnerState::CleanupChildless;
|
||||
|
||||
// Continue on to CleanupChildless, but only after checking IsCCNeeded
|
||||
// again.
|
||||
return {CCRunnerAction::None, Continue};
|
||||
|
||||
// CleanupChildless: do a stronger ForgetSkippable that removes nodes with
|
||||
// no children in the cycle collector graph. This state is split into 3
|
||||
// parts; the other Cleanup* actions will happen within the same callback
|
||||
// (unless the ForgetSkippable shrinks the purple buffer enough for the CC
|
||||
// to be skipped entirely.)
|
||||
case CCRunnerState::CleanupChildless:
|
||||
mCCRunnerState = CCRunnerState::CleanupContentUnbinder;
|
||||
return {CCRunnerAction::ForgetSkippable, Yield, RemoveChildless};
|
||||
|
||||
// CleanupContentUnbinder: continuing cleanup, clear out the content
|
||||
// unbinder.
|
||||
case CCRunnerState::CleanupContentUnbinder:
|
||||
if (aDeadline.IsNull()) {
|
||||
// Non-idle (waiting) callbacks skip the rest of the cleanup, but still
|
||||
// wait for another fire before the actual CC.
|
||||
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
// Running in an idle callback.
|
||||
|
||||
// The deadline passed, so go straight to CC in the next slice.
|
||||
if (aNow >= aDeadline) {
|
||||
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
mCCRunnerState = CCRunnerState::CleanupDeferred;
|
||||
return {CCRunnerAction::CleanupContentUnbinder, Continue};
|
||||
|
||||
// CleanupDeferred: continuing cleanup, do deferred deletion.
|
||||
case CCRunnerState::CleanupDeferred:
|
||||
MOZ_ASSERT(!aDeadline.IsNull(),
|
||||
"Should only be in CleanupDeferred state when idle");
|
||||
|
||||
// Our efforts to avoid a CC have failed. Let the timer fire once more
|
||||
// to trigger a CC.
|
||||
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
||||
if (aNow >= aDeadline) {
|
||||
// The deadline passed, go straight to CC in the next slice.
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
return {CCRunnerAction::CleanupDeferred, Yield};
|
||||
|
||||
// StartCycleCollection: start actually doing cycle collection slices.
|
||||
case CCRunnerState::StartCycleCollection:
|
||||
// We are in the final timer fire and still meet the conditions for
|
||||
// triggering a CC. Let RunCycleCollectorSlice finish the current IGC if
|
||||
// any, because that will allow us to include the GC time in the CC pause.
|
||||
mCCRunnerState = CCRunnerState::CycleCollecting;
|
||||
[[fallthrough]];
|
||||
|
||||
// CycleCollecting: continue running slices until done.
|
||||
case CCRunnerState::CycleCollecting:
|
||||
return {CCRunnerAction::CycleCollect, Yield};
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Unexpected CCRunner state");
|
||||
};
|
||||
}
|
||||
|
||||
GCRunnerStep CCGCScheduler::GetNextGCRunnerAction(TimeStamp aDeadline) {
|
||||
MOZ_ASSERT(mMajorGCReason != JS::GCReason::NO_REASON);
|
||||
|
||||
if (InIncrementalGC()) {
|
||||
return {GCRunnerAction::GCSlice, mMajorGCReason};
|
||||
}
|
||||
|
||||
if (mReadyForMajorGC) {
|
||||
return {GCRunnerAction::StartMajorGC, mMajorGCReason};
|
||||
}
|
||||
|
||||
return {GCRunnerAction::WaitToMajorGC, mMajorGCReason};
|
||||
}
|
||||
|
||||
inline js::SliceBudget CCGCScheduler::ComputeForgetSkippableBudget(
|
||||
TimeStamp aStartTimeStamp, TimeStamp aDeadline) {
|
||||
if (mForgetSkippableFrequencyStartTime.IsNull()) {
|
||||
mForgetSkippableFrequencyStartTime = aStartTimeStamp;
|
||||
} else if (aStartTimeStamp - mForgetSkippableFrequencyStartTime >
|
||||
kOneMinute) {
|
||||
TimeStamp startPlusMinute = mForgetSkippableFrequencyStartTime + kOneMinute;
|
||||
|
||||
// If we had forget skippables only at the beginning of the interval, we
|
||||
// still want to use the whole time, minute or more, for frequency
|
||||
// calculation. mLastForgetSkippableEndTime is needed if forget skippable
|
||||
// takes enough time to push the interval to be over a minute.
|
||||
TimeStamp endPoint = std::max(startPlusMinute, mLastForgetSkippableEndTime);
|
||||
|
||||
// Duration in minutes.
|
||||
double duration =
|
||||
(endPoint - mForgetSkippableFrequencyStartTime).ToSeconds() / 60;
|
||||
uint32_t frequencyPerMinute = uint32_t(mForgetSkippableCounter / duration);
|
||||
Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
|
||||
frequencyPerMinute);
|
||||
mForgetSkippableCounter = 0;
|
||||
mForgetSkippableFrequencyStartTime = aStartTimeStamp;
|
||||
}
|
||||
++mForgetSkippableCounter;
|
||||
|
||||
TimeDuration budgetTime =
|
||||
aDeadline ? (aDeadline - aStartTimeStamp) : kForgetSkippableSliceDuration;
|
||||
return js::SliceBudget(budgetTime);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
Загрузка…
Ссылка в новой задаче