Commit ae6a0b80 authored by ulan's avatar ulan Committed by Commit bot

Add mode to reduce memory usage in idle notification.

While the mutator is active, the idle time handler optimizes for latency by doing only incremental steps and scavenges.

When the mutator becomes inactive, the idle time handler forces few incremental GCs to reclaim memory and then stops until mutator is active again.

BUG=460090
LOG=N

Review URL: https://codereview.chromium.org/1105293004

Cr-Commit-Position: refs/heads/master@{#28300}
parent 010c515d
......@@ -12,8 +12,6 @@ namespace internal {
const double GCIdleTimeHandler::kConservativeTimeRatio = 0.9;
const size_t GCIdleTimeHandler::kMaxMarkCompactTimeInMs = 1000;
const size_t GCIdleTimeHandler::kMaxFinalIncrementalMarkCompactTimeInMs = 1000;
const int GCIdleTimeHandler::kMaxMarkCompactsInIdleRound = 2;
const int GCIdleTimeHandler::kIdleScavengeThreshold = 5;
const double GCIdleTimeHandler::kHighContextDisposalRate = 100;
const size_t GCIdleTimeHandler::kMinTimeForOverApproximatingWeakClosureInMs = 1;
......@@ -187,13 +185,56 @@ bool GCIdleTimeHandler::ShouldDoOverApproximateWeakClosure(
}
GCIdleTimeAction GCIdleTimeHandler::NothingOrDone() {
if (idle_times_which_made_no_progress_since_last_idle_round_ >=
kMaxNoProgressIdleTimesPerIdleRound) {
// The idle time handler has three modes and transitions between them
// as shown in the diagram:
//
// kReduceLatency -----> kReduceMemory -----> kDone
// ^ ^ | |
// | | | |
// | +------------------+ |
// | |
// +----------------------------------------+
//
// In kReduceLatency mode the handler only starts incremental marking
// if can_start_incremental_marking is false.
// In kReduceMemory mode the handler can force a new GC cycle by starting
// incremental marking even if can_start_incremental_marking is false. It can
// cause at most X idle GCs.
// In kDone mode the idle time handler does nothing.
//
// The initial mode is kReduceLatency.
//
// kReduceLatency => kReduceMemory transition happens if there were Y
// consecutive long idle notifications without any mutator GC. This is our
// notion of "mutator is idle".
//
// kReduceMemory => kDone transition happens after X idle GCs.
//
// kReduceMemory => kReduceLatency transition happens if N mutator GCs
// were performed meaning that the mutator is active.
//
// kDone => kReduceLatency transition happens if there were M mutator GCs or
// context was disposed.
//
// X = kMaxIdleMarkCompacts
// Y = kLongIdleNotificationsBeforeMutatorIsIdle
// N = #(idle GCs)
// M = kGCsBeforeMutatorIsActive
GCIdleTimeAction GCIdleTimeHandler::Compute(double idle_time_in_ms,
HeapState heap_state) {
Mode next_mode = NextMode(heap_state);
if (next_mode != mode_) {
mode_ = next_mode;
ResetCounters();
}
UpdateCounters(idle_time_in_ms);
if (mode_ == kDone) {
return GCIdleTimeAction::Done();
} else {
idle_times_which_made_no_progress_since_last_idle_round_++;
return GCIdleTimeAction::Nothing();
return Action(idle_time_in_ms, heap_state, mode_ == kReduceMemory);
}
}
......@@ -204,28 +245,23 @@ GCIdleTimeAction GCIdleTimeHandler::NothingOrDone() {
// a full GC.
// (2) If the new space is almost full and we can afford a Scavenge or if the
// next Scavenge will very likely take long, then a Scavenge is performed.
// (3) If there is currently no MarkCompact idle round going on, we start a
// new idle round if enough garbage was created. Otherwise we do not perform
// garbage collection to keep system utilization low.
// (4) If incremental marking is done, we perform a full garbage collection
// (3) If incremental marking is done, we perform a full garbage collection
// if we are allowed to still do full garbage collections during this idle
// round or if we are not allowed to start incremental marking. Otherwise we
// do not perform garbage collection to keep system utilization low.
// (5) If sweeping is in progress and we received a large enough idle time
// (4) If sweeping is in progress and we received a large enough idle time
// request, we finalize sweeping here.
// (6) If incremental marking is in progress, we perform a marking step. Note,
// (5) If incremental marking is in progress, we perform a marking step. Note,
// that this currently may trigger a full garbage collection.
GCIdleTimeAction GCIdleTimeHandler::Compute(double idle_time_in_ms,
HeapState heap_state) {
GCIdleTimeAction GCIdleTimeHandler::Action(double idle_time_in_ms,
const HeapState& heap_state,
bool reduce_memory) {
if (static_cast<int>(idle_time_in_ms) <= 0) {
if (heap_state.contexts_disposed > 0) {
StartIdleRound();
}
if (heap_state.incremental_marking_stopped) {
if (ShouldDoContextDisposalMarkCompact(
heap_state.contexts_disposed,
heap_state.contexts_disposal_rate)) {
return GCIdleTimeAction::FullGC();
return GCIdleTimeAction::FullGC(false);
}
}
return GCIdleTimeAction::Nothing();
......@@ -239,37 +275,99 @@ GCIdleTimeAction GCIdleTimeHandler::Compute(double idle_time_in_ms,
return GCIdleTimeAction::Scavenge();
}
if (IsMarkCompactIdleRoundFinished()) {
if (EnoughGarbageSinceLastIdleRound()) {
StartIdleRound();
} else {
return GCIdleTimeAction::Done();
}
}
if (heap_state.incremental_marking_stopped) {
if (heap_state.incremental_marking_stopped && reduce_memory) {
if (ShouldDoMarkCompact(static_cast<size_t>(idle_time_in_ms),
heap_state.size_of_objects,
heap_state.mark_compact_speed_in_bytes_per_ms)) {
return GCIdleTimeAction::FullGC();
return GCIdleTimeAction::FullGC(reduce_memory);
}
}
if (heap_state.sweeping_in_progress) {
if (heap_state.sweeping_completed) {
return GCIdleTimeAction::FinalizeSweeping();
} else {
return NothingOrDone();
return GCIdleTimeAction::Nothing();
}
}
if (heap_state.incremental_marking_stopped &&
!heap_state.can_start_incremental_marking) {
return NothingOrDone();
!heap_state.can_start_incremental_marking && !reduce_memory) {
return GCIdleTimeAction::Nothing();
}
size_t step_size = EstimateMarkingStepSize(
static_cast<size_t>(kIncrementalMarkingStepTimeInMs),
heap_state.incremental_marking_speed_in_bytes_per_ms);
return GCIdleTimeAction::IncrementalMarking(step_size);
return GCIdleTimeAction::IncrementalMarking(step_size, reduce_memory);
}
void GCIdleTimeHandler::UpdateCounters(double idle_time_in_ms) {
if (mode_ == kReduceLatency) {
int mutator_gcs = scavenges_ + mark_compacts_ - idle_mark_compacts_;
if (mutator_gcs > 0) {
// There was a mutator GC since the last notification.
long_idle_notifications_ = 0;
}
idle_mark_compacts_ = 0;
mark_compacts_ = 0;
scavenges_ = 0;
if (idle_time_in_ms >= kMinLongIdleTime) {
long_idle_notifications_ +=
(idle_time_in_ms >= kLargeLongIdleTime)
? kLongIdleNotificationsBeforeMutatorIsIdle
: 1;
}
}
}
void GCIdleTimeHandler::ResetCounters() {
long_idle_notifications_ = 0;
idle_mark_compacts_ = 0;
mark_compacts_ = 0;
scavenges_ = 0;
}
bool GCIdleTimeHandler::IsMutatorActive(int contexts_disposed, int gcs) {
return contexts_disposed > 0 || gcs >= kGCsBeforeMutatorIsActive;
}
bool GCIdleTimeHandler::IsMutatorIdle(int long_idle_notifications, int gcs) {
return gcs == 0 &&
long_idle_notifications >= kLongIdleNotificationsBeforeMutatorIsIdle;
}
GCIdleTimeHandler::Mode GCIdleTimeHandler::NextMode(
const HeapState& heap_state) {
DCHECK(mark_compacts_ >= idle_mark_compacts_);
int mutator_gcs = scavenges_ + mark_compacts_ - idle_mark_compacts_;
switch (mode_) {
case kDone:
DCHECK(idle_mark_compacts_ == 0);
if (IsMutatorActive(heap_state.contexts_disposed, mutator_gcs)) {
return kReduceLatency;
}
break;
case kReduceLatency:
if (IsMutatorIdle(long_idle_notifications_, mutator_gcs)) {
return kReduceMemory;
}
break;
case kReduceMemory:
if (idle_mark_compacts_ >= kMaxIdleMarkCompacts) {
return kDone;
}
if (mutator_gcs > idle_mark_compacts_) {
return kReduceLatency;
}
break;
}
return mode_;
}
}
}
......@@ -27,6 +27,7 @@ class GCIdleTimeAction {
result.type = DONE;
result.parameter = 0;
result.additional_work = false;
result.reduce_memory = false;
return result;
}
......@@ -35,14 +36,17 @@ class GCIdleTimeAction {
result.type = DO_NOTHING;
result.parameter = 0;
result.additional_work = false;
result.reduce_memory = false;
return result;
}
static GCIdleTimeAction IncrementalMarking(intptr_t step_size) {
static GCIdleTimeAction IncrementalMarking(intptr_t step_size,
bool reduce_memory) {
GCIdleTimeAction result;
result.type = DO_INCREMENTAL_MARKING;
result.parameter = step_size;
result.additional_work = false;
result.reduce_memory = reduce_memory;
return result;
}
......@@ -51,14 +55,18 @@ class GCIdleTimeAction {
result.type = DO_SCAVENGE;
result.parameter = 0;
result.additional_work = false;
// TODO(ulan): add reduce_memory argument and shrink new space size if
// reduce_memory = true.
result.reduce_memory = false;
return result;
}
static GCIdleTimeAction FullGC() {
static GCIdleTimeAction FullGC(bool reduce_memory) {
GCIdleTimeAction result;
result.type = DO_FULL_GC;
result.parameter = 0;
result.additional_work = false;
result.reduce_memory = reduce_memory;
return result;
}
......@@ -67,6 +75,7 @@ class GCIdleTimeAction {
result.type = DO_FINALIZE_SWEEPING;
result.parameter = 0;
result.additional_work = false;
result.reduce_memory = false;
return result;
}
......@@ -75,6 +84,7 @@ class GCIdleTimeAction {
GCIdleTimeActionType type;
intptr_t parameter;
bool additional_work;
bool reduce_memory;
};
......@@ -111,13 +121,6 @@ class GCIdleTimeHandler {
// EstimateFinalIncrementalMarkCompactTime.
static const size_t kMaxFinalIncrementalMarkCompactTimeInMs;
// Number of idle mark-compact events, after which idle handler will finish
// idle round.
static const int kMaxMarkCompactsInIdleRound;
// Number of scavenges that will trigger start of new idle round.
static const int kIdleScavengeThreshold;
// This is the maximum scheduled idle time. Note that it can be more than
// 16.66 ms when there is currently no rendering going on.
static const size_t kMaxScheduledIdleTime = 50;
......@@ -141,10 +144,22 @@ class GCIdleTimeHandler {
static const size_t kMinTimeForOverApproximatingWeakClosureInMs;
// Number of times we will return a Nothing action per Idle round despite
// having idle time available before we returning a Done action to ensure we
// don't keep scheduling idle tasks and making no progress.
static const int kMaxNoProgressIdleTimesPerIdleRound = 10;
// The number of idle MarkCompact GCs to perform before transitioning to
// the kDone mode.
static const int kMaxIdleMarkCompacts = 3;
// The number of mutator GCs before transitioning to the kReduceLatency mode.
static const int kGCsBeforeMutatorIsActive = 7;
// Mutator is considered idle if
// 1) there is an idle notification with time >= kLargeLongIdleTime,
// 2) or there are kLongIdleNotificationsBeforeMutatorIsIdle idle
// notifications
// with time >= kMinLongIdleTime and without any mutator GC in between.
static const int kMinLongIdleTime = kMaxFrameRenderingIdleTime + 1;
static const int kLargeLongIdleTime = 900;
static const int kLongIdleNotificationsBeforeMutatorIsIdle = 20;
class HeapState {
public:
......@@ -167,23 +182,19 @@ class GCIdleTimeHandler {
};
GCIdleTimeHandler()
: mark_compacts_since_idle_round_started_(0),
scavenges_since_last_idle_round_(0),
idle_times_which_made_no_progress_since_last_idle_round_(0) {}
: idle_mark_compacts_(0),
mark_compacts_(0),
scavenges_(0),
long_idle_notifications_(0),
mode_(kReduceLatency) {}
GCIdleTimeAction Compute(double idle_time_in_ms, HeapState heap_state);
void NotifyIdleMarkCompact() {
if (mark_compacts_since_idle_round_started_ < kMaxMarkCompactsInIdleRound) {
++mark_compacts_since_idle_round_started_;
if (mark_compacts_since_idle_round_started_ ==
kMaxMarkCompactsInIdleRound) {
scavenges_since_last_idle_round_ = 0;
}
}
}
void NotifyIdleMarkCompact() { ++idle_mark_compacts_; }
void NotifyScavenge() { ++scavenges_since_last_idle_round_; }
void NotifyMarkCompact() { ++mark_compacts_; }
void NotifyScavenge() { ++scavenges_; }
static size_t EstimateMarkingStepSize(size_t idle_time_in_ms,
size_t marking_speed_in_bytes_per_ms);
......@@ -212,24 +223,27 @@ class GCIdleTimeHandler {
size_t scavenger_speed_in_bytes_per_ms,
size_t new_space_allocation_throughput_in_bytes_per_ms);
private:
GCIdleTimeAction NothingOrDone();
enum Mode { kReduceLatency, kReduceMemory, kDone };
void StartIdleRound() {
mark_compacts_since_idle_round_started_ = 0;
idle_times_which_made_no_progress_since_last_idle_round_ = 0;
}
bool IsMarkCompactIdleRoundFinished() {
return mark_compacts_since_idle_round_started_ ==
kMaxMarkCompactsInIdleRound;
}
bool EnoughGarbageSinceLastIdleRound() {
return scavenges_since_last_idle_round_ >= kIdleScavengeThreshold;
}
Mode mode() { return mode_; }
int mark_compacts_since_idle_round_started_;
int scavenges_since_last_idle_round_;
int idle_times_which_made_no_progress_since_last_idle_round_;
private:
bool IsMutatorActive(int contexts_disposed, int gcs);
bool IsMutatorIdle(int long_idle_notifications, int gcs);
void UpdateCounters(double idle_time_in_ms);
void ResetCounters();
Mode NextMode(const HeapState& heap_state);
GCIdleTimeAction Action(double idle_time_in_ms, const HeapState& heap_state,
bool reduce_memory);
int idle_mark_compacts_;
int mark_compacts_;
int scavenges_;
// The number of long idle notifications with no mutator GC happening
// between the notifications.
int long_idle_notifications_;
Mode mode_;
DISALLOW_COPY_AND_ASSIGN(GCIdleTimeHandler);
};
......
......@@ -915,6 +915,13 @@ bool Heap::CollectGarbage(GarbageCollector collector, const char* gc_reason,
if (collector == MARK_COMPACTOR && FLAG_track_detached_contexts) {
isolate()->CheckDetachedContextsAfterGC();
}
if (collector == MARK_COMPACTOR) {
gc_idle_time_handler_.NotifyMarkCompact();
} else {
gc_idle_time_handler_.NotifyScavenge();
}
tracer()->Stop(collector);
}
......@@ -1642,8 +1649,6 @@ void Heap::Scavenge() {
LOG(isolate_, ResourceEvent("scavenge", "end"));
gc_state_ = NOT_IN_GC;
gc_idle_time_handler_.NotifyScavenge();
}
......@@ -4573,6 +4578,7 @@ bool Heap::TryFinalizeIdleIncrementalMarking(
static_cast<size_t>(idle_time_in_ms), size_of_objects,
final_incremental_mark_compact_speed_in_bytes_per_ms))) {
CollectAllGarbage(kNoGCFlags, "idle notification: finalize incremental");
gc_idle_time_handler_.NotifyIdleMarkCompact();
ReduceNewSpaceSize(is_long_idle_notification);
return true;
}
......@@ -4663,6 +4669,7 @@ bool Heap::IdleNotification(double deadline_in_seconds) {
break;
case DO_INCREMENTAL_MARKING: {
if (incremental_marking()->IsStopped()) {
// TODO(ulan): take reduce_memory into account.
incremental_marking()->Start();
}
double remaining_idle_time_in_ms = 0.0;
......
......@@ -2245,28 +2245,10 @@ TEST(ResetSharedFunctionInfoCountersDuringIncrementalMarking) {
IncrementalMarking* marking = CcTest::heap()->incremental_marking();
marking->Abort();
marking->Start();
// The following two calls will increment CcTest::heap()->global_ic_age().
const double kLongIdlePauseInSeconds = 1.0;
// The following calls will increment CcTest::heap()->global_ic_age().
CcTest::isolate()->ContextDisposedNotification();
CcTest::isolate()->IdleNotificationDeadline(
(v8::base::TimeTicks::HighResolutionNow().ToInternalValue() /
static_cast<double>(v8::base::Time::kMicrosecondsPerSecond)) +
kLongIdlePauseInSeconds);
while (!marking->IsStopped() && !marking->IsComplete()) {
marking->Step(1 * MB, IncrementalMarking::NO_GC_VIA_STACK_GUARD);
}
if (!marking->IsStopped() || marking->should_hurry()) {
// We don't normally finish a GC via Step(), we normally finish by
// setting the stack guard and then do the final steps in the stack
// guard interrupt. But here we didn't ask for that, and there is no
// JS code running to trigger the interrupt, so we explicitly finalize
// here.
CcTest::heap()->CollectAllGarbage(Heap::kFinalizeIncrementalMarkingMask,
"Test finalizing incremental mark-sweep");
}
SimulateIncrementalMarking(CcTest::heap());
CcTest::heap()->CollectAllGarbage();
CHECK_EQ(CcTest::heap()->global_ic_age(), f->shared()->ic_age());
CHECK_EQ(0, f->shared()->opt_count());
CHECK_EQ(0, f->shared()->code()->profiler_ticks());
......@@ -2305,13 +2287,8 @@ TEST(ResetSharedFunctionInfoCountersDuringMarkSweep) {
CcTest::heap()->incremental_marking()->Abort();
// The following two calls will increment CcTest::heap()->global_ic_age().
// Since incremental marking is off, IdleNotification will do full GC.
const double kLongIdlePauseInSeconds = 1.0;
CcTest::isolate()->ContextDisposedNotification();
CcTest::isolate()->IdleNotificationDeadline(
(v8::base::TimeTicks::HighResolutionNow().ToInternalValue() /
static_cast<double>(v8::base::Time::kMicrosecondsPerSecond)) +
kLongIdlePauseInSeconds);
CcTest::heap()->CollectAllGarbage();
CHECK_EQ(CcTest::heap()->global_ic_age(), f->shared()->ic_age());
CHECK_EQ(0, f->shared()->opt_count());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment