Commit 0a82f091 authored by ulan's avatar ulan Committed by Commit bot

[heap] Add a guard for restarting the memory reducer after mark-compact.

Currently it is possible to get into a cycle of
mark-compact -> memory reducer -> mark-compact -> memory reducer ...
where the memory reducer does not free memory.

This patch ensures that the memory reducer restarts only if the
committed memory increased by sufficient amount after the last run.

BUG=

Review-Url: https://chromiumcodereview.appspot.com/2433933005
Cr-Commit-Position: refs/heads/master@{#40457}
parent 9780e970
...@@ -1016,6 +1016,7 @@ bool Heap::CollectGarbage(GarbageCollector collector, ...@@ -1016,6 +1016,7 @@ bool Heap::CollectGarbage(GarbageCollector collector,
(committed_memory_before - committed_memory_after) > MB || (committed_memory_before - committed_memory_after) > MB ||
HasHighFragmentation(used_memory_after, committed_memory_after) || HasHighFragmentation(used_memory_after, committed_memory_after) ||
(detached_contexts()->length() > 0); (detached_contexts()->length() > 0);
event.committed_memory = committed_memory_after;
if (deserialization_complete_) { if (deserialization_complete_) {
memory_reducer_->NotifyMarkCompact(event); memory_reducer_->NotifyMarkCompact(event);
} }
......
...@@ -17,6 +17,8 @@ const int MemoryReducer::kLongDelayMs = 8000; ...@@ -17,6 +17,8 @@ const int MemoryReducer::kLongDelayMs = 8000;
const int MemoryReducer::kShortDelayMs = 500; const int MemoryReducer::kShortDelayMs = 500;
const int MemoryReducer::kWatchdogDelayMs = 100000; const int MemoryReducer::kWatchdogDelayMs = 100000;
const int MemoryReducer::kMaxNumberOfGCs = 3; const int MemoryReducer::kMaxNumberOfGCs = 3;
const double MemoryReducer::kCommittedMemoryFactor = 1.1;
const size_t MemoryReducer::kCommittedMemoryDelta = 10 * MB;
MemoryReducer::TimerTask::TimerTask(MemoryReducer* memory_reducer) MemoryReducer::TimerTask::TimerTask(MemoryReducer* memory_reducer)
: CancelableTask(memory_reducer->heap()->isolate()), : CancelableTask(memory_reducer->heap()->isolate()),
...@@ -48,6 +50,7 @@ void MemoryReducer::TimerTask::RunInternal() { ...@@ -48,6 +50,7 @@ void MemoryReducer::TimerTask::RunInternal() {
event.can_start_incremental_gc = event.can_start_incremental_gc =
heap->incremental_marking()->IsStopped() && heap->incremental_marking()->IsStopped() &&
(heap->incremental_marking()->CanBeActivated() || optimize_for_memory); (heap->incremental_marking()->CanBeActivated() || optimize_for_memory);
event.committed_memory = heap->CommittedOldGenerationMemory();
memory_reducer_->NotifyTimer(event); memory_reducer_->NotifyTimer(event);
} }
...@@ -138,17 +141,30 @@ bool MemoryReducer::WatchdogGC(const State& state, const Event& event) { ...@@ -138,17 +141,30 @@ bool MemoryReducer::WatchdogGC(const State& state, const Event& event) {
MemoryReducer::State MemoryReducer::Step(const State& state, MemoryReducer::State MemoryReducer::Step(const State& state,
const Event& event) { const Event& event) {
if (!FLAG_incremental_marking || !FLAG_memory_reducer) { if (!FLAG_incremental_marking || !FLAG_memory_reducer) {
return State(kDone, 0, 0, state.last_gc_time_ms); return State(kDone, 0, 0, state.last_gc_time_ms, 0);
} }
switch (state.action) { switch (state.action) {
case kDone: case kDone:
if (event.type == kTimer) { if (event.type == kTimer) {
return state; return state;
} else if (event.type == kMarkCompact) {
if (event.committed_memory <
Max(static_cast<size_t>(state.committed_memory_at_last_run *
kCommittedMemoryFactor),
state.committed_memory_at_last_run + kCommittedMemoryDelta)) {
return state;
} else {
return State(kWait, 0, event.time_ms + kLongDelayMs,
event.type == kMarkCompact ? event.time_ms
: state.last_gc_time_ms,
0);
}
} else { } else {
DCHECK(event.type == kPossibleGarbage || event.type == kMarkCompact); DCHECK_EQ(kPossibleGarbage, event.type);
return State( return State(
kWait, 0, event.time_ms + kLongDelayMs, kWait, 0, event.time_ms + kLongDelayMs,
event.type == kMarkCompact ? event.time_ms : state.last_gc_time_ms); event.type == kMarkCompact ? event.time_ms : state.last_gc_time_ms,
0);
} }
case kWait: case kWait:
switch (event.type) { switch (event.type) {
...@@ -156,23 +172,24 @@ MemoryReducer::State MemoryReducer::Step(const State& state, ...@@ -156,23 +172,24 @@ MemoryReducer::State MemoryReducer::Step(const State& state,
return state; return state;
case kTimer: case kTimer:
if (state.started_gcs >= kMaxNumberOfGCs) { if (state.started_gcs >= kMaxNumberOfGCs) {
return State(kDone, kMaxNumberOfGCs, 0.0, state.last_gc_time_ms); return State(kDone, kMaxNumberOfGCs, 0.0, state.last_gc_time_ms,
event.committed_memory);
} else if (event.can_start_incremental_gc && } else if (event.can_start_incremental_gc &&
(event.should_start_incremental_gc || (event.should_start_incremental_gc ||
WatchdogGC(state, event))) { WatchdogGC(state, event))) {
if (state.next_gc_start_ms <= event.time_ms) { if (state.next_gc_start_ms <= event.time_ms) {
return State(kRun, state.started_gcs + 1, 0.0, return State(kRun, state.started_gcs + 1, 0.0,
state.last_gc_time_ms); state.last_gc_time_ms, 0);
} else { } else {
return state; return state;
} }
} else { } else {
return State(kWait, state.started_gcs, event.time_ms + kLongDelayMs, return State(kWait, state.started_gcs, event.time_ms + kLongDelayMs,
state.last_gc_time_ms); state.last_gc_time_ms, 0);
} }
case kMarkCompact: case kMarkCompact:
return State(kWait, state.started_gcs, event.time_ms + kLongDelayMs, return State(kWait, state.started_gcs, event.time_ms + kLongDelayMs,
event.time_ms); event.time_ms, 0);
} }
case kRun: case kRun:
if (event.type != kMarkCompact) { if (event.type != kMarkCompact) {
...@@ -181,14 +198,15 @@ MemoryReducer::State MemoryReducer::Step(const State& state, ...@@ -181,14 +198,15 @@ MemoryReducer::State MemoryReducer::Step(const State& state,
if (state.started_gcs < kMaxNumberOfGCs && if (state.started_gcs < kMaxNumberOfGCs &&
(event.next_gc_likely_to_collect_more || state.started_gcs == 1)) { (event.next_gc_likely_to_collect_more || state.started_gcs == 1)) {
return State(kWait, state.started_gcs, event.time_ms + kShortDelayMs, return State(kWait, state.started_gcs, event.time_ms + kShortDelayMs,
event.time_ms); event.time_ms, 0);
} else { } else {
return State(kDone, kMaxNumberOfGCs, 0.0, event.time_ms); return State(kDone, kMaxNumberOfGCs, 0.0, event.time_ms,
event.committed_memory);
} }
} }
} }
UNREACHABLE(); UNREACHABLE();
return State(kDone, 0, 0, 0.0); // Make the compiler happy. return State(kDone, 0, 0, 0.0, 0); // Make the compiler happy.
} }
...@@ -204,8 +222,7 @@ void MemoryReducer::ScheduleTimer(double time_ms, double delay_ms) { ...@@ -204,8 +222,7 @@ void MemoryReducer::ScheduleTimer(double time_ms, double delay_ms) {
isolate, timer_task, (delay_ms + kSlackMs) / 1000.0); isolate, timer_task, (delay_ms + kSlackMs) / 1000.0);
} }
void MemoryReducer::TearDown() { state_ = State(kDone, 0, 0, 0.0, 0); }
void MemoryReducer::TearDown() { state_ = State(kDone, 0, 0, 0.0); }
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -86,15 +86,17 @@ class V8_EXPORT_PRIVATE MemoryReducer { ...@@ -86,15 +86,17 @@ class V8_EXPORT_PRIVATE MemoryReducer {
struct State { struct State {
State(Action action, int started_gcs, double next_gc_start_ms, State(Action action, int started_gcs, double next_gc_start_ms,
double last_gc_time_ms) double last_gc_time_ms, size_t committed_memory_at_last_run)
: action(action), : action(action),
started_gcs(started_gcs), started_gcs(started_gcs),
next_gc_start_ms(next_gc_start_ms), next_gc_start_ms(next_gc_start_ms),
last_gc_time_ms(last_gc_time_ms) {} last_gc_time_ms(last_gc_time_ms),
committed_memory_at_last_run(committed_memory_at_last_run) {}
Action action; Action action;
int started_gcs; int started_gcs;
double next_gc_start_ms; double next_gc_start_ms;
double last_gc_time_ms; double last_gc_time_ms;
size_t committed_memory_at_last_run;
}; };
enum EventType { kTimer, kMarkCompact, kPossibleGarbage }; enum EventType { kTimer, kMarkCompact, kPossibleGarbage };
...@@ -102,6 +104,7 @@ class V8_EXPORT_PRIVATE MemoryReducer { ...@@ -102,6 +104,7 @@ class V8_EXPORT_PRIVATE MemoryReducer {
struct Event { struct Event {
EventType type; EventType type;
double time_ms; double time_ms;
size_t committed_memory;
bool next_gc_likely_to_collect_more; bool next_gc_likely_to_collect_more;
bool should_start_incremental_gc; bool should_start_incremental_gc;
bool can_start_incremental_gc; bool can_start_incremental_gc;
...@@ -109,7 +112,7 @@ class V8_EXPORT_PRIVATE MemoryReducer { ...@@ -109,7 +112,7 @@ class V8_EXPORT_PRIVATE MemoryReducer {
explicit MemoryReducer(Heap* heap) explicit MemoryReducer(Heap* heap)
: heap_(heap), : heap_(heap),
state_(kDone, 0, 0.0, 0.0), state_(kDone, 0, 0.0, 0.0, 0),
js_calls_counter_(0), js_calls_counter_(0),
js_calls_sample_time_ms_(0.0) {} js_calls_sample_time_ms_(0.0) {}
// Callbacks. // Callbacks.
...@@ -126,6 +129,12 @@ class V8_EXPORT_PRIVATE MemoryReducer { ...@@ -126,6 +129,12 @@ class V8_EXPORT_PRIVATE MemoryReducer {
static const int kShortDelayMs; static const int kShortDelayMs;
static const int kWatchdogDelayMs; static const int kWatchdogDelayMs;
static const int kMaxNumberOfGCs; static const int kMaxNumberOfGCs;
// The committed memory has to increase by at least this factor since the
// last run in order to trigger a new run after mark-compact.
static const double kCommittedMemoryFactor;
// The committed memory has to increase by at least this amount since the
// last run in order to trigger a new run after mark-compact.
static const size_t kCommittedMemoryDelta;
Heap* heap() { return heap_; } Heap* heap() { return heap_; }
......
...@@ -12,39 +12,44 @@ namespace v8 { ...@@ -12,39 +12,44 @@ namespace v8 {
namespace internal { namespace internal {
MemoryReducer::State DoneState() { MemoryReducer::State DoneState() {
return MemoryReducer::State(MemoryReducer::kDone, 0, 0.0, 1.0); return MemoryReducer::State(MemoryReducer::kDone, 0, 0.0, 1.0, 0);
} }
MemoryReducer::State DoneState(size_t committed_memory) {
return MemoryReducer::State(MemoryReducer::kDone, 0, 0.0, 1.0,
committed_memory);
}
MemoryReducer::State WaitState(int started_gcs, double next_gc_start_ms) { MemoryReducer::State WaitState(int started_gcs, double next_gc_start_ms) {
return MemoryReducer::State(MemoryReducer::kWait, started_gcs, return MemoryReducer::State(MemoryReducer::kWait, started_gcs,
next_gc_start_ms, 1.0); next_gc_start_ms, 1.0, 0);
} }
MemoryReducer::State RunState(int started_gcs, double next_gc_start_ms) { MemoryReducer::State RunState(int started_gcs, double next_gc_start_ms) {
return MemoryReducer::State(MemoryReducer::kRun, started_gcs, return MemoryReducer::State(MemoryReducer::kRun, started_gcs,
next_gc_start_ms, 1.0); next_gc_start_ms, 1.0, 0);
} }
MemoryReducer::Event MarkCompactEvent(double time_ms, MemoryReducer::Event MarkCompactEvent(double time_ms,
bool next_gc_likely_to_collect_more) { bool next_gc_likely_to_collect_more,
size_t committed_memory) {
MemoryReducer::Event event; MemoryReducer::Event event;
event.type = MemoryReducer::kMarkCompact; event.type = MemoryReducer::kMarkCompact;
event.time_ms = time_ms; event.time_ms = time_ms;
event.next_gc_likely_to_collect_more = next_gc_likely_to_collect_more; event.next_gc_likely_to_collect_more = next_gc_likely_to_collect_more;
event.committed_memory = committed_memory;
return event; return event;
} }
MemoryReducer::Event MarkCompactEventGarbageLeft(double time_ms,
MemoryReducer::Event MarkCompactEventGarbageLeft(double time_ms) { size_t committed_memory) {
return MarkCompactEvent(time_ms, true); return MarkCompactEvent(time_ms, true, committed_memory);
} }
MemoryReducer::Event MarkCompactEventNoGarbageLeft(double time_ms,
MemoryReducer::Event MarkCompactEventNoGarbageLeft(double time_ms) { size_t committed_memory) {
return MarkCompactEvent(time_ms, false); return MarkCompactEvent(time_ms, false, committed_memory);
} }
...@@ -93,6 +98,19 @@ TEST(MemoryReducer, FromDoneToDone) { ...@@ -93,6 +98,19 @@ TEST(MemoryReducer, FromDoneToDone) {
state1 = MemoryReducer::Step(state0, TimerEventPendingGC(0)); state1 = MemoryReducer::Step(state0, TimerEventPendingGC(0));
EXPECT_EQ(MemoryReducer::kDone, state1.action); EXPECT_EQ(MemoryReducer::kDone, state1.action);
state1 = MemoryReducer::Step(
state0,
MarkCompactEventGarbageLeft(0, MemoryReducer::kCommittedMemoryDelta - 1));
EXPECT_EQ(MemoryReducer::kDone, state1.action);
state0 = DoneState(1000 * MB);
state1 = MemoryReducer::Step(
state0, MarkCompactEventGarbageLeft(
0, static_cast<size_t>(
1000 * MB * MemoryReducer::kCommittedMemoryFactor) -
1));
EXPECT_EQ(MemoryReducer::kDone, state1.action);
} }
...@@ -101,13 +119,17 @@ TEST(MemoryReducer, FromDoneToWait) { ...@@ -101,13 +119,17 @@ TEST(MemoryReducer, FromDoneToWait) {
MemoryReducer::State state0(DoneState()), state1(DoneState()); MemoryReducer::State state0(DoneState()), state1(DoneState());
state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2)); state1 = MemoryReducer::Step(
state0,
MarkCompactEventGarbageLeft(2, MemoryReducer::kCommittedMemoryDelta));
EXPECT_EQ(MemoryReducer::kWait, state1.action); EXPECT_EQ(MemoryReducer::kWait, state1.action);
EXPECT_EQ(MemoryReducer::kLongDelayMs + 2, state1.next_gc_start_ms); EXPECT_EQ(MemoryReducer::kLongDelayMs + 2, state1.next_gc_start_ms);
EXPECT_EQ(0, state1.started_gcs); EXPECT_EQ(0, state1.started_gcs);
EXPECT_EQ(2, state1.last_gc_time_ms); EXPECT_EQ(2, state1.last_gc_time_ms);
state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2)); state1 = MemoryReducer::Step(
state0,
MarkCompactEventNoGarbageLeft(2, MemoryReducer::kCommittedMemoryDelta));
EXPECT_EQ(MemoryReducer::kWait, state1.action); EXPECT_EQ(MemoryReducer::kWait, state1.action);
EXPECT_EQ(MemoryReducer::kLongDelayMs + 2, state1.next_gc_start_ms); EXPECT_EQ(MemoryReducer::kLongDelayMs + 2, state1.next_gc_start_ms);
EXPECT_EQ(0, state1.started_gcs); EXPECT_EQ(0, state1.started_gcs);
...@@ -118,6 +140,16 @@ TEST(MemoryReducer, FromDoneToWait) { ...@@ -118,6 +140,16 @@ TEST(MemoryReducer, FromDoneToWait) {
EXPECT_EQ(MemoryReducer::kLongDelayMs, state1.next_gc_start_ms); EXPECT_EQ(MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
EXPECT_EQ(0, state1.started_gcs); EXPECT_EQ(0, state1.started_gcs);
EXPECT_EQ(state0.last_gc_time_ms, state1.last_gc_time_ms); EXPECT_EQ(state0.last_gc_time_ms, state1.last_gc_time_ms);
state0 = DoneState(1000 * MB);
state1 = MemoryReducer::Step(
state0, MarkCompactEventGarbageLeft(
2, static_cast<size_t>(
1000 * MB * MemoryReducer::kCommittedMemoryFactor)));
EXPECT_EQ(MemoryReducer::kWait, state1.action);
EXPECT_EQ(MemoryReducer::kLongDelayMs + 2, state1.next_gc_start_ms);
EXPECT_EQ(0, state1.started_gcs);
EXPECT_EQ(2, state1.last_gc_time_ms);
} }
...@@ -147,13 +179,13 @@ TEST(MemoryReducer, FromWaitToWait) { ...@@ -147,13 +179,13 @@ TEST(MemoryReducer, FromWaitToWait) {
EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms); EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
EXPECT_EQ(state0.started_gcs, state1.started_gcs); EXPECT_EQ(state0.started_gcs, state1.started_gcs);
state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000)); state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000, 0));
EXPECT_EQ(MemoryReducer::kWait, state1.action); EXPECT_EQ(MemoryReducer::kWait, state1.action);
EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms); EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
EXPECT_EQ(state0.started_gcs, state1.started_gcs); EXPECT_EQ(state0.started_gcs, state1.started_gcs);
EXPECT_EQ(2000, state1.last_gc_time_ms); EXPECT_EQ(2000, state1.last_gc_time_ms);
state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000)); state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000, 0));
EXPECT_EQ(MemoryReducer::kWait, state1.action); EXPECT_EQ(MemoryReducer::kWait, state1.action);
EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms); EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
EXPECT_EQ(state0.started_gcs, state1.started_gcs); EXPECT_EQ(state0.started_gcs, state1.started_gcs);
...@@ -262,7 +294,7 @@ TEST(MemoryReducer, FromRunToDone) { ...@@ -262,7 +294,7 @@ TEST(MemoryReducer, FromRunToDone) {
MemoryReducer::State state0(RunState(2, 0.0)), state1(DoneState()); MemoryReducer::State state0(RunState(2, 0.0)), state1(DoneState());
state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000)); state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000, 0));
EXPECT_EQ(MemoryReducer::kDone, state1.action); EXPECT_EQ(MemoryReducer::kDone, state1.action);
EXPECT_EQ(0, state1.next_gc_start_ms); EXPECT_EQ(0, state1.next_gc_start_ms);
EXPECT_EQ(MemoryReducer::kMaxNumberOfGCs, state1.started_gcs); EXPECT_EQ(MemoryReducer::kMaxNumberOfGCs, state1.started_gcs);
...@@ -270,7 +302,7 @@ TEST(MemoryReducer, FromRunToDone) { ...@@ -270,7 +302,7 @@ TEST(MemoryReducer, FromRunToDone) {
state0.started_gcs = MemoryReducer::kMaxNumberOfGCs; state0.started_gcs = MemoryReducer::kMaxNumberOfGCs;
state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000)); state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000, 0));
EXPECT_EQ(MemoryReducer::kDone, state1.action); EXPECT_EQ(MemoryReducer::kDone, state1.action);
EXPECT_EQ(0, state1.next_gc_start_ms); EXPECT_EQ(0, state1.next_gc_start_ms);
EXPECT_EQ(2000, state1.last_gc_time_ms); EXPECT_EQ(2000, state1.last_gc_time_ms);
...@@ -282,7 +314,7 @@ TEST(MemoryReducer, FromRunToWait) { ...@@ -282,7 +314,7 @@ TEST(MemoryReducer, FromRunToWait) {
MemoryReducer::State state0(RunState(2, 0.0)), state1(DoneState()); MemoryReducer::State state0(RunState(2, 0.0)), state1(DoneState());
state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000)); state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000, 0));
EXPECT_EQ(MemoryReducer::kWait, state1.action); EXPECT_EQ(MemoryReducer::kWait, state1.action);
EXPECT_EQ(2000 + MemoryReducer::kShortDelayMs, state1.next_gc_start_ms); EXPECT_EQ(2000 + MemoryReducer::kShortDelayMs, state1.next_gc_start_ms);
EXPECT_EQ(state0.started_gcs, state1.started_gcs); EXPECT_EQ(state0.started_gcs, state1.started_gcs);
...@@ -290,7 +322,7 @@ TEST(MemoryReducer, FromRunToWait) { ...@@ -290,7 +322,7 @@ TEST(MemoryReducer, FromRunToWait) {
state0.started_gcs = 1; state0.started_gcs = 1;
state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000)); state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000, 0));
EXPECT_EQ(MemoryReducer::kWait, state1.action); EXPECT_EQ(MemoryReducer::kWait, state1.action);
EXPECT_EQ(2000 + MemoryReducer::kShortDelayMs, state1.next_gc_start_ms); EXPECT_EQ(2000 + MemoryReducer::kShortDelayMs, state1.next_gc_start_ms);
EXPECT_EQ(state0.started_gcs, state1.started_gcs); EXPECT_EQ(state0.started_gcs, state1.started_gcs);
......
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