"src/heap/mark-compact-inl.h",
"src/heap/mark-compact.cc",
"src/heap/mark-compact.h",
+ "src/heap/memory-reducer.cc",
+ "src/heap/memory-reducer.h",
"src/heap/objects-visiting-inl.h",
"src/heap/objects-visiting.cc",
"src/heap/objects-visiting.h",
PrintF("contexts_disposal_rate=%f ", contexts_disposal_rate);
PrintF("size_of_objects=%" V8_PTR_PREFIX "d ", size_of_objects);
PrintF("incremental_marking_stopped=%d ", incremental_marking_stopped);
- PrintF("can_start_incremental_marking=%d ", can_start_incremental_marking);
PrintF("sweeping_in_progress=%d ", sweeping_in_progress);
PrintF("has_low_allocation_rate=%d", has_low_allocation_rate);
PrintF("mark_compact_speed=%" V8_PTR_PREFIX "d ",
GCIdleTimeAction GCIdleTimeHandler::NothingOrDone() {
- if (idle_times_which_made_no_progress_per_mode_ >=
- kMaxNoProgressIdleTimesPerMode) {
+ if (idle_times_which_made_no_progress_ >= kMaxNoProgressIdleTimes) {
return GCIdleTimeAction::Done();
} else {
- idle_times_which_made_no_progress_per_mode_++;
+ idle_times_which_made_no_progress_++;
return GCIdleTimeAction::Nothing();
}
}
-// 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 {
- return Action(idle_time_in_ms, heap_state, mode_ == kReduceMemory);
- }
-}
-
-
// The following logic is implemented by the controller:
// (1) If we don't have any idle time, do nothing, unless a context was
// disposed, incremental marking is stopped, and the heap is small. Then do
// we do nothing until the context disposal rate becomes lower.
// (3) If the new space is almost full and we can affort a scavenge or if the
// next scavenge will very likely take long, then a scavenge is performed.
-// (4) 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.
-// (5) 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.
-// (6) 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.
-// (7) 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::Action(double idle_time_in_ms,
- const HeapState& heap_state,
- bool reduce_memory) {
+GCIdleTimeAction GCIdleTimeHandler::Compute(double idle_time_in_ms,
+ HeapState heap_state) {
if (static_cast<int>(idle_time_in_ms) <= 0) {
if (heap_state.incremental_marking_stopped) {
if (ShouldDoContextDisposalMarkCompact(
heap_state.contexts_disposed,
heap_state.contexts_disposal_rate)) {
- return GCIdleTimeAction::FullGC(false);
+ return GCIdleTimeAction::FullGC();
}
}
return GCIdleTimeAction::Nothing();
return GCIdleTimeAction::Scavenge();
}
- 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(reduce_memory);
- }
- }
-
if (heap_state.sweeping_in_progress) {
if (heap_state.sweeping_completed) {
return GCIdleTimeAction::FinalizeSweeping();
}
}
- if (!FLAG_incremental_marking ||
- (heap_state.incremental_marking_stopped &&
- !heap_state.can_start_incremental_marking && !reduce_memory)) {
- return NothingOrDone();
+ if (!FLAG_incremental_marking || heap_state.incremental_marking_stopped) {
+ return GCIdleTimeAction::Done();
}
size_t step_size = EstimateMarkingStepSize(
static_cast<size_t>(kIncrementalMarkingStepTimeInMs),
heap_state.incremental_marking_speed_in_bytes_per_ms);
- return GCIdleTimeAction::IncrementalMarking(step_size, reduce_memory);
+ return GCIdleTimeAction::IncrementalMarking(step_size);
}
-void GCIdleTimeHandler::UpdateCounters(double idle_time_in_ms) {
- if (mode_ == kReduceLatency) {
- int gcs = scavenges_ + mark_compacts_;
- if (gcs > 0) {
- // There was a GC since the last notification.
- long_idle_notifications_ = 0;
- background_idle_notifications_ = 0;
- }
- idle_mark_compacts_ = 0;
- mark_compacts_ = 0;
- scavenges_ = 0;
- if (idle_time_in_ms >= kMinBackgroundIdleTime) {
- background_idle_notifications_++;
- } else if (idle_time_in_ms >= kMinLongIdleTime) {
- long_idle_notifications_++;
- }
- }
-}
-
-
-void GCIdleTimeHandler::ResetCounters() {
- long_idle_notifications_ = 0;
- background_idle_notifications_ = 0;
- idle_mark_compacts_ = 0;
- mark_compacts_ = 0;
- scavenges_ = 0;
- idle_times_which_made_no_progress_per_mode_ = 0;
-}
-
-
-bool GCIdleTimeHandler::IsMutatorActive(int contexts_disposed,
- int mark_compacts) {
- return contexts_disposed > 0 ||
- mark_compacts >= kMarkCompactsBeforeMutatorIsActive;
-}
-
-
-bool GCIdleTimeHandler::IsMutatorIdle(int long_idle_notifications,
- int background_idle_notifications,
- int mutator_gcs) {
- return mutator_gcs == 0 &&
- (long_idle_notifications >=
- kLongIdleNotificationsBeforeMutatorIsIdle ||
- background_idle_notifications >=
- kBackgroundIdleNotificationsBeforeMutatorIsIdle);
-}
-
-
-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, mark_compacts_)) {
- return kReduceLatency;
- }
- break;
- case kReduceLatency:
- if (IsMutatorIdle(long_idle_notifications_,
- background_idle_notifications_, mutator_gcs)) {
- return kReduceMemory;
- }
- break;
- case kReduceMemory:
- if (idle_mark_compacts_ >= kMaxIdleMarkCompacts ||
- (idle_mark_compacts_ > 0 && !next_gc_likely_to_collect_more_)) {
- return kDone;
- }
- if (mutator_gcs > idle_mark_compacts_) {
- return kReduceLatency;
- }
- break;
- }
- return mode_;
-}
}
}
result.type = DONE;
result.parameter = 0;
result.additional_work = false;
- result.reduce_memory = false;
return result;
}
result.type = DO_NOTHING;
result.parameter = 0;
result.additional_work = false;
- result.reduce_memory = false;
return result;
}
- static GCIdleTimeAction IncrementalMarking(intptr_t step_size,
- bool reduce_memory) {
+ static GCIdleTimeAction IncrementalMarking(intptr_t step_size) {
GCIdleTimeAction result;
result.type = DO_INCREMENTAL_MARKING;
result.parameter = step_size;
result.additional_work = false;
- result.reduce_memory = reduce_memory;
return result;
}
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(bool reduce_memory) {
+ static GCIdleTimeAction FullGC() {
GCIdleTimeAction result;
result.type = DO_FULL_GC;
result.parameter = 0;
result.additional_work = false;
- result.reduce_memory = reduce_memory;
return result;
}
result.type = DO_FINALIZE_SWEEPING;
result.parameter = 0;
result.additional_work = false;
- result.reduce_memory = false;
return result;
}
GCIdleTimeActionType type;
intptr_t parameter;
bool additional_work;
- bool reduce_memory;
};
// The maximum idle time when frames are rendered is 16.66ms.
static const size_t kMaxFrameRenderingIdleTime = 17;
+ static const int kMinBackgroundIdleTime = 900;
+
// We conservatively assume that in the next kTimeUntilNextIdleEvent ms
// no idle notification happens.
static const size_t kTimeUntilNextIdleEvent = 100;
static const size_t kMinTimeForOverApproximatingWeakClosureInMs;
- // The number of idle MarkCompact GCs to perform before transitioning to
- // the kDone mode.
- static const int kMaxIdleMarkCompacts = 3;
-
- // The number of mutator MarkCompact GCs before transitioning to the
- // kReduceLatency mode.
- static const int kMarkCompactsBeforeMutatorIsActive = 1;
-
- // Mutator is considered idle if
- // 1) there are N idle notification with time >= kMinBackgroundIdleTime,
- // 2) or there are M idle notifications with time >= kMinLongIdleTime
- // without any mutator GC in between.
- // Where N = kBackgroundIdleNotificationsBeforeMutatorIsIdle,
- // M = kLongIdleNotificationsBeforeMutatorIsIdle
- static const int kMinLongIdleTime = kMaxFrameRenderingIdleTime + 1;
- static const int kMinBackgroundIdleTime = 900;
- static const int kBackgroundIdleNotificationsBeforeMutatorIsIdle = 2;
- static const int kLongIdleNotificationsBeforeMutatorIsIdle = 50;
// Number of times we will return a Nothing action in the current mode
// 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 kMaxNoProgressIdleTimesPerMode = 10;
+ static const int kMaxNoProgressIdleTimes = 10;
class HeapState {
public:
double contexts_disposal_rate;
size_t size_of_objects;
bool incremental_marking_stopped;
- bool can_start_incremental_marking;
bool sweeping_in_progress;
bool sweeping_completed;
bool has_low_allocation_rate;
size_t new_space_allocation_throughput_in_bytes_per_ms;
};
- GCIdleTimeHandler()
- : idle_mark_compacts_(0),
- mark_compacts_(0),
- scavenges_(0),
- long_idle_notifications_(0),
- background_idle_notifications_(0),
- idle_times_which_made_no_progress_per_mode_(0),
- next_gc_likely_to_collect_more_(false),
- mode_(kReduceLatency) {}
+ GCIdleTimeHandler() : idle_times_which_made_no_progress_(0) {}
GCIdleTimeAction Compute(double idle_time_in_ms, HeapState heap_state);
- void NotifyIdleMarkCompact() { ++idle_mark_compacts_; }
-
- void NotifyMarkCompact(bool next_gc_likely_to_collect_more) {
- next_gc_likely_to_collect_more_ = next_gc_likely_to_collect_more;
- ++mark_compacts_;
- }
-
- void NotifyScavenge() { ++scavenges_; }
+ void ResetNoProgressCounter() { idle_times_which_made_no_progress_ = 0; }
static size_t EstimateMarkingStepSize(size_t idle_time_in_ms,
size_t marking_speed_in_bytes_per_ms);
size_t scavenger_speed_in_bytes_per_ms,
size_t new_space_allocation_throughput_in_bytes_per_ms);
- enum Mode { kReduceLatency, kReduceMemory, kDone };
-
- Mode mode() { return mode_; }
-
private:
- bool IsMutatorActive(int contexts_disposed, int gcs);
- bool IsMutatorIdle(int long_idle_notifications,
- int background_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);
GCIdleTimeAction NothingOrDone();
- int idle_mark_compacts_;
- int mark_compacts_;
- int scavenges_;
- // The number of long idle notifications with no GC happening
- // between the notifications.
- int long_idle_notifications_;
- // The number of background idle notifications with no GC happening
- // between the notifications.
- int background_idle_notifications_;
- // Idle notifications with no progress in the current mode.
- int idle_times_which_made_no_progress_per_mode_;
-
- bool next_gc_likely_to_collect_more_;
-
- Mode mode_;
+ // Idle notifications with no progress.
+ int idle_times_which_made_no_progress_;
DISALLOW_COPY_AND_ASSIGN(GCIdleTimeHandler);
};
#include "src/heap/gc-idle-time-handler.h"
#include "src/heap/incremental-marking.h"
#include "src/heap/mark-compact.h"
+#include "src/heap/memory-reducer.h"
#include "src/heap/objects-visiting-inl.h"
#include "src/heap/objects-visiting.h"
#include "src/heap/store-buffer.h"
allocation_timeout_(0),
#endif // DEBUG
old_generation_allocation_limit_(initial_old_generation_size_),
- idle_old_generation_allocation_limit_(
- kMinimumOldGenerationAllocationLimit),
old_gen_exhausted_(false),
inline_allocation_disabled_(false),
store_buffer_rebuilder_(store_buffer()),
store_buffer_(this),
marking_(this),
incremental_marking_(this),
+ memory_reducer_(this),
full_codegen_bytes_generated_(0),
crankshaft_codegen_bytes_generated_(0),
new_space_allocation_counter_(0),
}
bool next_gc_likely_to_collect_more = false;
+ intptr_t committed_memory_before;
+
+ if (collector == MARK_COMPACTOR) {
+ committed_memory_before = CommittedOldGenerationMemory();
+ }
{
tracer()->Start(collector, gc_reason, collector_reason);
}
if (collector == MARK_COMPACTOR) {
- gc_idle_time_handler_.NotifyMarkCompact(next_gc_likely_to_collect_more);
- } else {
- gc_idle_time_handler_.NotifyScavenge();
+ intptr_t committed_memory_after = CommittedOldGenerationMemory();
+ intptr_t used_memory_after = PromotedSpaceSizeOfObjects();
+ MemoryReducer::Event event;
+ event.type = MemoryReducer::kMarkCompact;
+ event.time_ms = MonotonicallyIncreasingTimeInMs();
+ // Trigger one more GC if
+ // - this GC decreased committed memory,
+ // - there is high fragmentation,
+ // - there are live detached contexts.
+ event.next_gc_likely_to_collect_more =
+ (committed_memory_before - committed_memory_after) > MB ||
+ HasHighFragmentation(used_memory_after, committed_memory_after) ||
+ (detached_contexts()->length() > 0);
+ memory_reducer_.NotifyMarkCompact(event);
}
tracer()->Stop(collector);
AgeInlineCaches();
set_retained_maps(ArrayList::cast(empty_fixed_array()));
tracer()->AddContextDisposalTime(base::OS::TimeCurrentMillis());
+ MemoryReducer::Event event;
+ event.type = MemoryReducer::kContextDisposed;
+ event.time_ms = MonotonicallyIncreasingTimeInMs();
+ memory_reducer_.NotifyContextDisposed(event);
return ++contexts_disposed_;
}
+void Heap::StartIdleIncrementalMarking() {
+ gc_idle_time_handler_.ResetNoProgressCounter();
+ incremental_marking()->Start(kReduceMemoryFootprintMask);
+}
+
+
void Heap::MoveElements(FixedArray* array, int dst_index, int src_index,
int len) {
if (len == 0) return;
}
+bool Heap::HasHighFragmentation() {
+ intptr_t used = PromotedSpaceSizeOfObjects();
+ intptr_t committed = CommittedOldGenerationMemory();
+ return HasHighFragmentation(used, committed);
+}
+
+
+bool Heap::HasHighFragmentation(intptr_t used, intptr_t committed) {
+ const intptr_t kSlack = 16 * MB;
+ // Fragmentation is high if committed > 2 * used + kSlack.
+ // Rewrite the exression to avoid overflow.
+ return committed - used > used + kSlack;
+}
+
+
void Heap::ReduceNewSpaceSize() {
if (!FLAG_predictable && HasLowAllocationRate()) {
new_space_.Shrink();
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();
return true;
}
return false;
heap_state.new_space_capacity = new_space_.Capacity();
heap_state.new_space_allocation_throughput_in_bytes_per_ms =
tracer()->NewSpaceAllocationThroughputInBytesPerMillisecond();
- heap_state.has_low_allocation_rate = HasLowAllocationRate();
- intptr_t limit = old_generation_allocation_limit_;
- if (heap_state.has_low_allocation_rate) {
- limit = idle_old_generation_allocation_limit_;
- }
- heap_state.can_start_incremental_marking =
- incremental_marking()->CanBeActivated() &&
- HeapIsFullEnoughToStartIncrementalMarking(limit) &&
- !mark_compact_collector()->sweeping_in_progress();
return heap_state;
}
result = true;
break;
case DO_INCREMENTAL_MARKING: {
- if (incremental_marking()->IsStopped()) {
- incremental_marking()->Start(
- action.reduce_memory ? kReduceMemoryFootprintMask : kNoGCFlags);
- }
+ DCHECK(!incremental_marking()->IsStopped());
double remaining_idle_time_in_ms = 0.0;
do {
incremental_marking()->Step(
break;
}
case DO_FULL_GC: {
- if (action.reduce_memory) {
- isolate_->compilation_cache()->Clear();
- }
- if (contexts_disposed_) {
- HistogramTimerScope scope(isolate_->counters()->gc_context());
- CollectAllGarbage(kNoGCFlags, "idle notification: contexts disposed");
- } else {
- CollectAllGarbage(kReduceMemoryFootprintMask,
- "idle notification: finalize idle round");
- }
- gc_idle_time_handler_.NotifyIdleMarkCompact();
+ DCHECK(contexts_disposed_ > 0);
+ HistogramTimerScope scope(isolate_->counters()->gc_context());
+ CollectAllGarbage(kNoGCFlags, "idle notification: contexts disposed");
break;
}
case DO_SCAVENGE:
factor = kMinHeapGrowingFactor;
}
- // TODO(hpayer): Investigate if idle_old_generation_allocation_limit_ is still
- // needed after taking the allocation rate for the old generation limit into
- // account.
- double idle_factor = Min(factor, kMaxHeapGrowingFactorIdle);
-
old_generation_allocation_limit_ =
CalculateOldGenerationAllocationLimit(factor, old_gen_size);
- idle_old_generation_allocation_limit_ =
- CalculateOldGenerationAllocationLimit(idle_factor, old_gen_size);
if (FLAG_trace_gc_verbose) {
- PrintIsolate(
- isolate_,
- "Grow: old size: %" V8_PTR_PREFIX "d KB, new limit: %" V8_PTR_PREFIX
- "d KB (%.1f), new idle limit: %" V8_PTR_PREFIX "d KB (%.1f)\n",
- old_gen_size / KB, old_generation_allocation_limit_ / KB, factor,
- idle_old_generation_allocation_limit_ / KB, idle_factor);
+ PrintIsolate(isolate_, "Grow: old size: %" V8_PTR_PREFIX
+ "d KB, new limit: %" V8_PTR_PREFIX "d KB (%.1f)\n",
+ old_gen_size / KB, old_generation_allocation_limit_ / KB,
+ factor);
}
}
#include "src/heap/gc-tracer.h"
#include "src/heap/incremental-marking.h"
#include "src/heap/mark-compact.h"
+#include "src/heap/memory-reducer.h"
#include "src/heap/objects-visiting.h"
#include "src/heap/spaces.h"
#include "src/heap/store-buffer.h"
// Notify the heap that a context has been disposed.
int NotifyContextDisposed(bool dependant_context);
+ // Start incremental marking and ensure that idle time handler can perform
+ // incremental steps.
+ void StartIdleIncrementalMarking();
+
inline void increment_scan_on_scavenge_pages() {
scan_on_scavenge_pages_++;
if (FLAG_gc_verbose) {
// An ArrayBuffer moved from new space to old space.
void PromoteArrayBuffer(Object* buffer);
+ bool HasLowAllocationRate();
+ bool HasHighFragmentation();
+ bool HasHighFragmentation(intptr_t used, intptr_t committed);
+
protected:
// Methods made available to tests.
// generation and on every allocation in large object space.
intptr_t old_generation_allocation_limit_;
- // The allocation limit when there is >16.66ms idle time in the idle time
- // handler.
- intptr_t idle_old_generation_allocation_limit_;
-
// Indicates that an allocation has failed in the old generation since the
// last GC.
bool old_gen_exhausted_;
bool HasLowYoungGenerationAllocationRate();
bool HasLowOldGenerationAllocationRate();
- bool HasLowAllocationRate();
void ReduceNewSpaceSize();
GCIdleTimeHandler gc_idle_time_handler_;
+ MemoryReducer memory_reducer_;
+
// These two counters are monotomically increasing and never reset.
size_t full_codegen_bytes_generated_;
size_t crankshaft_codegen_bytes_generated_;
--- /dev/null
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/heap/memory-reducer.h"
+
+#include "src/flags.h"
+#include "src/heap/heap.h"
+#include "src/utils.h"
+#include "src/v8.h"
+
+namespace v8 {
+namespace internal {
+
+const int MemoryReducer::kLongDelayMs = 5000;
+const int MemoryReducer::kShortDelayMs = 500;
+const int MemoryReducer::kMaxNumberOfGCs = 3;
+
+
+void MemoryReducer::TimerTask::Run() {
+ Heap* heap = memory_reducer_->heap();
+ Event event;
+ event.type = kTimer;
+ event.time_ms = heap->MonotonicallyIncreasingTimeInMs();
+ event.low_allocation_rate = heap->HasLowAllocationRate();
+ event.can_start_incremental_gc =
+ heap->incremental_marking()->IsStopped() &&
+ heap->incremental_marking()->CanBeActivated();
+ memory_reducer_->NotifyTimer(event);
+}
+
+
+void MemoryReducer::NotifyTimer(const Event& event) {
+ DCHECK_EQ(kTimer, event.type);
+ DCHECK_EQ(kWait, state_.action);
+ state_ = Step(state_, event);
+ if (state_.action == kRun) {
+ DCHECK(heap()->incremental_marking()->IsStopped());
+ DCHECK(FLAG_incremental_marking);
+ heap()->StartIdleIncrementalMarking();
+ if (FLAG_trace_gc_verbose) {
+ PrintIsolate(heap()->isolate(), "Memory reducer: started GC #%d\n",
+ state_.started_gcs);
+ }
+ } else if (state_.action == kWait) {
+ // Re-schedule the timer.
+ ScheduleTimer(state_.next_gc_start_ms - event.time_ms);
+ if (FLAG_trace_gc_verbose) {
+ PrintIsolate(heap()->isolate(), "Memory reducer: waiting for %.f ms\n",
+ state_.next_gc_start_ms - event.time_ms);
+ }
+ }
+}
+
+
+void MemoryReducer::NotifyMarkCompact(const Event& event) {
+ DCHECK_EQ(kMarkCompact, event.type);
+ Action old_action = state_.action;
+ state_ = Step(state_, event);
+ if (old_action != kWait && state_.action == kWait) {
+ // If we are transitioning to the WAIT state, start the timer.
+ ScheduleTimer(state_.next_gc_start_ms - event.time_ms);
+ }
+ if (old_action == kRun) {
+ if (FLAG_trace_gc_verbose) {
+ PrintIsolate(heap()->isolate(), "Memory reducer: finished GC #%d (%s)\n",
+ state_.started_gcs,
+ state_.action == kWait ? "will do more" : "done");
+ }
+ }
+}
+
+
+void MemoryReducer::NotifyContextDisposed(const Event& event) {
+ DCHECK_EQ(kContextDisposed, event.type);
+ Action old_action = state_.action;
+ state_ = Step(state_, event);
+ if (old_action != kWait && state_.action == kWait) {
+ // If we are transitioning to the WAIT state, start the timer.
+ ScheduleTimer(state_.next_gc_start_ms - event.time_ms);
+ }
+}
+
+
+// For specification of this function see the comment for MemoryReducer class.
+MemoryReducer::State MemoryReducer::Step(const State& state,
+ const Event& event) {
+ if (!FLAG_incremental_marking) {
+ return State(kDone, 0, 0);
+ }
+ switch (state.action) {
+ case kDone:
+ if (event.type == kTimer) {
+ return state;
+ } else {
+ DCHECK(event.type == kContextDisposed || event.type == kMarkCompact);
+ return State(kWait, 0, event.time_ms + kLongDelayMs);
+ }
+ case kWait:
+ if (event.type == kContextDisposed) {
+ return state;
+ } else if (event.type == kTimer && event.can_start_incremental_gc &&
+ event.low_allocation_rate) {
+ if (state.next_gc_start_ms <= event.time_ms) {
+ return State(kRun, state.started_gcs + 1, 0.0);
+ } else {
+ return state;
+ }
+ } else {
+ return State(kWait, state.started_gcs, event.time_ms + kLongDelayMs);
+ }
+ case kRun:
+ if (event.type != kMarkCompact) {
+ return state;
+ } else {
+ if (state.started_gcs < kMaxNumberOfGCs &&
+ (event.next_gc_likely_to_collect_more || state.started_gcs == 1)) {
+ return State(kWait, state.started_gcs, event.time_ms + kShortDelayMs);
+ } else {
+ return State(kDone, 0, 0.0);
+ }
+ }
+ }
+ UNREACHABLE();
+ return State(kDone, 0, 0); // Make the compiler happy.
+}
+
+
+void MemoryReducer::ScheduleTimer(double delay_ms) {
+ DCHECK(delay_ms > 0);
+ // Leave some room for precision error in task scheduler.
+ const double kSlackMs = 100;
+ v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(heap()->isolate());
+ V8::GetCurrentPlatform()->CallDelayedOnForegroundThread(
+ isolate, new MemoryReducer::TimerTask(this),
+ (delay_ms + kSlackMs) / 1000.0);
+}
+
+} // internal
+} // v8
--- /dev/null
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_HEAP_memory_reducer_H
+#define V8_HEAP_memory_reducer_H
+
+#include "include/v8-platform.h"
+#include "src/base/macros.h"
+
+namespace v8 {
+namespace internal {
+
+class Heap;
+
+
+// The goal of the MemoryReducer class is to detect transition of the mutator
+// from high allocation phase to low allocation phase and to collect potential
+// garbage created in the high allocation phase.
+//
+// The class implements an automaton with the following states and transitions.
+//
+// States:
+// - DONE
+// - WAIT <started_gcs> <next_gc_start_ms>
+// - RUN <started_gcs>
+// The <started_gcs> is an integer in range from 0..kMaxNumberOfGCs that stores
+// the number of GCs initiated by the MemoryReducer since it left the DONE
+// state.
+// The <next_gc_start_ms> is a double that stores the earliest time the next GC
+// can be initiated by the MemoryReducer.
+// The DONE state means that the MemoryReducer is not active.
+// The WAIT state means that the MemoryReducer is waiting for mutator allocation
+// rate to drop. The check for the allocation rate happens in the timer task
+// callback.
+// The RUN state means that the MemoryReducer started incremental marking and is
+// waiting for it to finish. Incremental marking steps are performed as usual
+// in the idle notification and in the mutator.
+//
+// Transitions:
+// DONE -> WAIT 0 (now_ms + long_delay_ms) happens:
+// - on context disposal,
+// - at the end of mark-compact GC initiated by the mutator.
+// This signals that there is potential garbage to be collected.
+//
+// WAIT n x -> WAIT n (now_ms + long_delay_ms) happens:
+// - on mark-compact GC initiated by the mutator,
+// - in the timer callback if the mutator allocation rate is high or
+// incremental GC is in progress.
+//
+// WAIT n x -> RUN (n+1) happens:
+// - in the timer callback if the mutator allocation rate is low
+// and now_ms >= x and there is no incremental GC in progress.
+// The MemoryReducer starts incremental marking on this transition.
+//
+// RUN n -> DONE happens:
+// - at end of the incremental GC initiated by the MemoryReducer if
+// (n > 1 and there is no more garbage to be collected) or
+// n == kMaxNumberOfGCs.
+// RUN n -> WAIT n (now_ms + short_delay_ms) happens:
+// - at end of the incremental GC initiated by the MemoryReducer if
+// (n == 1 or there is more garbage to be collected) and
+// n < kMaxNumberOfGCs.
+//
+// now_ms is the current time, long_delay_ms and short_delay_ms are constants.
+class MemoryReducer {
+ public:
+ enum Action { kDone, kWait, kRun };
+
+ struct State {
+ State(Action action, int started_gcs, double next_gc_start_ms)
+ : action(action),
+ started_gcs(started_gcs),
+ next_gc_start_ms(next_gc_start_ms) {}
+ Action action;
+ int started_gcs;
+ double next_gc_start_ms;
+ };
+
+ enum EventType {
+ kTimer,
+ kMarkCompact,
+ kContextDisposed,
+ };
+
+ struct Event {
+ EventType type;
+ double time_ms;
+ bool low_allocation_rate;
+ bool next_gc_likely_to_collect_more;
+ bool can_start_incremental_gc;
+ };
+
+ explicit MemoryReducer(Heap* heap) : heap_(heap), state_(kDone, 0, 0.0) {}
+ // Callbacks.
+ void NotifyTimer(const Event& event);
+ void NotifyMarkCompact(const Event& event);
+ void NotifyScavenge(const Event& event);
+ void NotifyContextDisposed(const Event& event);
+ // The step function that computes the next state from the current state and
+ // the incoming event.
+ static State Step(const State& state, const Event& event);
+ // Posts a timer task that will call NotifyTimer after the given delay.
+ void ScheduleTimer(double delay_ms);
+
+ static const int kLongDelayMs;
+ static const int kShortDelayMs;
+ static const int kMaxNumberOfGCs;
+
+ Heap* heap() { return heap_; }
+
+ private:
+ class TimerTask : public v8::Task {
+ public:
+ explicit TimerTask(MemoryReducer* memory_reducer)
+ : memory_reducer_(memory_reducer) {}
+ virtual ~TimerTask() {}
+
+ private:
+ // v8::Task overrides.
+ void Run() override;
+ MemoryReducer* memory_reducer_;
+ DISALLOW_COPY_AND_ASSIGN(TimerTask);
+ };
+
+ Heap* heap_;
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryReducer);
+};
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_HEAP_memory_reducer_H
// Test that idle notification can be handled and eventually collects garbage.
TEST(TestIdleNotification) {
+ if (!i::FLAG_incremental_marking) return;
const intptr_t MB = 1024 * 1024;
const double IdlePauseInSeconds = 1.0;
LocalContext env;
CHECK_GT(size_with_garbage, initial_size + MB);
bool finished = false;
for (int i = 0; i < 200 && !finished; i++) {
+ if (i < 10 && CcTest::heap()->incremental_marking()->IsStopped()) {
+ CcTest::heap()->StartIdleIncrementalMarking();
+ }
finished = env->GetIsolate()->IdleNotificationDeadline(
(v8::base::TimeTicks::HighResolutionNow().ToInternalValue() /
static_cast<double>(v8::base::Time::kMicrosecondsPerSecond)) +
result.contexts_disposal_rate = GCIdleTimeHandler::kHighContextDisposalRate;
result.size_of_objects = kSizeOfObjects;
result.incremental_marking_stopped = false;
- result.can_start_incremental_marking = true;
result.sweeping_in_progress = false;
result.sweeping_completed = false;
result.mark_compact_speed_in_bytes_per_ms = kMarkCompactSpeed;
return result;
}
- void TransitionToReduceMemoryMode(
- const GCIdleTimeHandler::HeapState& heap_state) {
- handler()->NotifyScavenge();
- EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
- double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
- int limit = GCIdleTimeHandler::kLongIdleNotificationsBeforeMutatorIsIdle;
- bool incremental = !heap_state.incremental_marking_stopped ||
- heap_state.can_start_incremental_marking;
- for (int i = 0; i < limit; i++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- if (incremental) {
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- } else {
- EXPECT_TRUE(DO_NOTHING == action.type || DONE == action.type);
- }
- }
- handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
- }
-
- void TransitionToDoneMode(const GCIdleTimeHandler::HeapState& heap_state,
- double idle_time_ms,
- GCIdleTimeActionType expected) {
- EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
- int limit = GCIdleTimeHandler::kMaxIdleMarkCompacts;
- for (int i = 0; i < limit; i++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(expected, action.type);
- EXPECT_TRUE(action.reduce_memory);
- handler()->NotifyMarkCompact(true);
- handler()->NotifyIdleMarkCompact();
- }
- handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(GCIdleTimeHandler::kDone, handler()->mode());
- }
-
- void TransitionToReduceLatencyMode(
- const GCIdleTimeHandler::HeapState& heap_state) {
- EXPECT_EQ(GCIdleTimeHandler::kDone, handler()->mode());
- int limit = GCIdleTimeHandler::kMarkCompactsBeforeMutatorIsActive;
- double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
- for (int i = 0; i < limit; i++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DONE, action.type);
- handler()->NotifyMarkCompact(true);
- }
- handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
- }
-
static const size_t kSizeOfObjects = 100 * MB;
static const size_t kMarkCompactSpeed = 200 * KB;
static const size_t kMarkingSpeed = 200 * KB;
static const size_t kScavengeSpeed = 100 * KB;
static const size_t kNewSpaceCapacity = 1 * MB;
static const size_t kNewSpaceAllocationThroughput = 10 * KB;
- static const int kMaxNotifications = 1000;
+ static const int kMaxNotifications = 100;
private:
GCIdleTimeHandler handler_;
heap_state.contexts_disposed = 1;
heap_state.incremental_marking_stopped = true;
double idle_time_ms = 0;
- for (int mode = 0; mode < 1; mode++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_NOTHING, action.type);
- TransitionToReduceMemoryMode(heap_state);
- }
+ GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ EXPECT_EQ(DO_NOTHING, action.type);
}
GCIdleTimeHandler::kHighContextDisposalRate - 1;
heap_state.incremental_marking_stopped = true;
double idle_time_ms = 0;
- for (int mode = 0; mode < 1; mode++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_FULL_GC, action.type);
- heap_state.contexts_disposal_rate = 0.0;
- TransitionToReduceMemoryMode(heap_state);
- }
+ GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ EXPECT_EQ(DO_FULL_GC, action.type);
}
heap_state.contexts_disposal_rate = 1.0;
heap_state.incremental_marking_stopped = true;
double idle_time_ms = 0;
- for (int mode = 0; mode < 1; mode++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_FULL_GC, action.type);
- heap_state.contexts_disposal_rate = 0.0;
- TransitionToReduceMemoryMode(heap_state);
- }
+ GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ EXPECT_EQ(DO_FULL_GC, action.type);
}
heap_state.contexts_disposed = 1;
heap_state.contexts_disposal_rate =
GCIdleTimeHandler::kHighContextDisposalRate;
- heap_state.incremental_marking_stopped = true;
size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
double idle_time_ms =
static_cast<double>(heap_state.size_of_objects / speed - 1);
- for (int mode = 0; mode < 1; mode++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- heap_state.contexts_disposal_rate = 0.0;
- TransitionToReduceMemoryMode(heap_state);
- }
+ GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
}
size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
double idle_time_ms =
static_cast<double>(heap_state.size_of_objects / speed - 1);
- for (int mode = 0; mode < 1; mode++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- heap_state.contexts_disposal_rate = 0.0;
- TransitionToReduceMemoryMode(heap_state);
- }
+ GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
}
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
size_t speed = heap_state.incremental_marking_speed_in_bytes_per_ms;
double idle_time_ms = 10;
- for (int mode = 0; mode < 1; mode++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- EXPECT_GT(speed * static_cast<size_t>(idle_time_ms),
- static_cast<size_t>(action.parameter));
- EXPECT_LT(0, action.parameter);
- TransitionToReduceMemoryMode(heap_state);
- }
+ GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
+ EXPECT_GT(speed * static_cast<size_t>(idle_time_ms),
+ static_cast<size_t>(action.parameter));
+ EXPECT_LT(0, action.parameter);
}
TEST_F(GCIdleTimeHandlerTest, IncrementalMarking2) {
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
- heap_state.incremental_marking_stopped = true;
size_t speed = heap_state.incremental_marking_speed_in_bytes_per_ms;
double idle_time_ms = 10;
- for (int mode = 0; mode < 1; mode++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- EXPECT_GT(speed * static_cast<size_t>(idle_time_ms),
- static_cast<size_t>(action.parameter));
- EXPECT_LT(0, action.parameter);
- TransitionToReduceMemoryMode(heap_state);
- }
+ GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
+ EXPECT_GT(speed * static_cast<size_t>(idle_time_ms),
+ static_cast<size_t>(action.parameter));
+ EXPECT_LT(0, action.parameter);
}
TEST_F(GCIdleTimeHandlerTest, NotEnoughTime) {
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
double idle_time_ms =
static_cast<double>(heap_state.size_of_objects / speed - 1);
GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_NOTHING, action.type);
- TransitionToReduceMemoryMode(heap_state);
- action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
+ EXPECT_EQ(DONE, action.type);
}
TEST_F(GCIdleTimeHandlerTest, FinalizeSweeping) {
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- for (int mode = 0; mode < 1; mode++) {
- heap_state.sweeping_in_progress = true;
- heap_state.sweeping_completed = true;
- double idle_time_ms = 10.0;
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_FINALIZE_SWEEPING, action.type);
- heap_state.sweeping_in_progress = false;
- heap_state.sweeping_completed = false;
- TransitionToReduceMemoryMode(heap_state);
- }
+ heap_state.sweeping_in_progress = true;
+ heap_state.sweeping_completed = true;
+ double idle_time_ms = 10.0;
+ GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ EXPECT_EQ(DO_FINALIZE_SWEEPING, action.type);
}
TEST_F(GCIdleTimeHandlerTest, CannotFinalizeSweeping) {
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- for (int mode = 0; mode < 1; mode++) {
- heap_state.sweeping_in_progress = true;
- heap_state.sweeping_completed = false;
- double idle_time_ms = 10.0;
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_NOTHING, action.type);
- heap_state.sweeping_in_progress = false;
- heap_state.sweeping_completed = false;
- TransitionToReduceMemoryMode(heap_state);
- }
+ heap_state.sweeping_in_progress = true;
+ heap_state.sweeping_completed = false;
+ double idle_time_ms = 10.0;
+ GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ EXPECT_EQ(DO_NOTHING, action.type);
}
TEST_F(GCIdleTimeHandlerTest, Scavenge) {
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
int idle_time_ms = 10;
- for (int mode = 0; mode < 1; mode++) {
- heap_state.used_new_space_size =
- heap_state.new_space_capacity -
- (kNewSpaceAllocationThroughput * idle_time_ms);
- GCIdleTimeAction action =
- handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
- EXPECT_EQ(DO_SCAVENGE, action.type);
- heap_state.used_new_space_size = 0;
- TransitionToReduceMemoryMode(heap_state);
- }
+ heap_state.used_new_space_size =
+ heap_state.new_space_capacity -
+ (kNewSpaceAllocationThroughput * idle_time_ms);
+ GCIdleTimeAction action =
+ handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
+ EXPECT_EQ(DO_SCAVENGE, action.type);
+ heap_state.used_new_space_size = 0;
}
TEST_F(GCIdleTimeHandlerTest, ScavengeAndDone) {
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
int idle_time_ms = 10;
- heap_state.can_start_incremental_marking = false;
- heap_state.incremental_marking_stopped = true;
- for (int mode = 0; mode < 1; mode++) {
- heap_state.used_new_space_size =
- heap_state.new_space_capacity -
- (kNewSpaceAllocationThroughput * idle_time_ms);
- GCIdleTimeAction action =
- handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
- EXPECT_EQ(DO_SCAVENGE, action.type);
- heap_state.used_new_space_size = 0;
- action = handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
- EXPECT_EQ(DO_NOTHING, action.type);
- TransitionToReduceMemoryMode(heap_state);
- }
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, StopEventually1) {
- GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
- heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
- bool stopped = false;
- for (int i = 0; i < kMaxNotifications && !stopped; i++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- if (action.type == DO_INCREMENTAL_MARKING || action.type == DO_FULL_GC) {
- handler()->NotifyMarkCompact(true);
- handler()->NotifyIdleMarkCompact();
- }
- if (action.type == DONE) stopped = true;
- }
- EXPECT_TRUE(stopped);
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, StopEventually2) {
- GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
- heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
- double idle_time_ms =
- static_cast<double>(heap_state.size_of_objects / speed + 1);
- TransitionToReduceMemoryMode(heap_state);
- TransitionToDoneMode(heap_state, idle_time_ms, DO_FULL_GC);
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DONE, action.type);
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, StopEventually3) {
- GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- double idle_time_ms = 10;
- TransitionToReduceMemoryMode(heap_state);
- TransitionToDoneMode(heap_state, idle_time_ms, DO_INCREMENTAL_MARKING);
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+ heap_state.used_new_space_size =
+ heap_state.new_space_capacity -
+ (kNewSpaceAllocationThroughput * idle_time_ms);
+ GCIdleTimeAction action =
+ handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
+ EXPECT_EQ(DO_SCAVENGE, action.type);
+ heap_state.used_new_space_size = 0;
+ action = handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
EXPECT_EQ(DONE, action.type);
}
-TEST_F(GCIdleTimeHandlerTest, ContinueAfterStop1) {
+TEST_F(GCIdleTimeHandlerTest, DoNotStartIncrementalMarking) {
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
- double idle_time_ms =
- static_cast<double>(heap_state.size_of_objects / speed + 1);
- TransitionToReduceMemoryMode(heap_state);
- TransitionToDoneMode(heap_state, idle_time_ms, DO_FULL_GC);
+ double idle_time_ms = 10.0;
GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
EXPECT_EQ(DONE, action.type);
- TransitionToReduceLatencyMode(heap_state);
- heap_state.can_start_incremental_marking = true;
- action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- EXPECT_FALSE(action.reduce_memory);
- EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
}
-TEST_F(GCIdleTimeHandlerTest, ContinueAfterStop2) {
+TEST_F(GCIdleTimeHandlerTest, ContinueAfterStop) {
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- double idle_time_ms = 10;
- TransitionToReduceMemoryMode(heap_state);
- TransitionToDoneMode(heap_state, idle_time_ms, DO_INCREMENTAL_MARKING);
+ double idle_time_ms = 10.0;
GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
EXPECT_EQ(DONE, action.type);
- TransitionToReduceLatencyMode(heap_state);
- heap_state.can_start_incremental_marking = true;
+ heap_state.incremental_marking_stopped = false;
action = handler()->Compute(idle_time_ms, heap_state);
EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- EXPECT_FALSE(action.reduce_memory);
- EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
}
TEST_F(GCIdleTimeHandlerTest, SmallIdleTimeNothingToDo) {
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
for (int i = 0; i < kMaxNotifications; i++) {
GCIdleTimeAction action = handler()->Compute(10, heap_state);
EXPECT_TRUE(DO_NOTHING == action.type || DONE == action.type);
}
-TEST_F(GCIdleTimeHandlerTest, StayInReduceLatencyModeBecauseOfScavenges) {
- GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
- heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
- int limit = GCIdleTimeHandler::kLongIdleNotificationsBeforeMutatorIsIdle;
- for (int i = 0; i < kMaxNotifications; i++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_TRUE(DO_NOTHING == action.type || DONE == action.type);
- if ((i + 1) % limit == 0) handler()->NotifyScavenge();
- EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
- }
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, StayInReduceLatencyModeBecauseOfMarkCompacts) {
- GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
- heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
- int limit = GCIdleTimeHandler::kLongIdleNotificationsBeforeMutatorIsIdle;
- for (int i = 0; i < kMaxNotifications; i++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_TRUE(DO_NOTHING == action.type || DONE == action.type);
- if ((i + 1) % limit == 0) handler()->NotifyMarkCompact(true);
- EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
- }
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, ReduceMemoryToReduceLatency) {
- GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
- heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
- int limit = GCIdleTimeHandler::kMaxIdleMarkCompacts;
- for (int idle_gc = 0; idle_gc < limit; idle_gc++) {
- TransitionToReduceMemoryMode(heap_state);
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- EXPECT_TRUE(action.reduce_memory);
- EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
- for (int i = 0; i < idle_gc; i++) {
- action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- EXPECT_TRUE(action.reduce_memory);
- // ReduceMemory mode should tolerate one mutator GC per idle GC.
- handler()->NotifyScavenge();
- // Notify idle GC.
- handler()->NotifyMarkCompact(true);
- handler()->NotifyIdleMarkCompact();
- }
- // Transition to ReduceLatency mode after doing |idle_gc| idle GCs.
- handler()->NotifyScavenge();
- action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_NOTHING, action.type);
- EXPECT_FALSE(action.reduce_memory);
- EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
- }
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, ReduceMemoryToDone) {
- GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
- heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
- double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
- int limit = GCIdleTimeHandler::kMaxIdleMarkCompacts;
- TransitionToReduceMemoryMode(heap_state);
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- EXPECT_TRUE(action.reduce_memory);
- for (int i = 0; i < limit; i++) {
- action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- EXPECT_TRUE(action.reduce_memory);
- EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
- // ReduceMemory mode should tolerate one mutator GC per idle GC.
- handler()->NotifyScavenge();
- // Notify idle GC.
- handler()->NotifyMarkCompact(true);
- handler()->NotifyIdleMarkCompact();
- }
- action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DONE, action.type);
-}
-
-
TEST_F(GCIdleTimeHandlerTest, DoneIfNotMakingProgressOnSweeping) {
// Regression test for crbug.com/489323.
GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
// Simulate sweeping being in-progress but not complete.
heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
heap_state.sweeping_in_progress = true;
heap_state.sweeping_completed = false;
double idle_time_ms = 10.0;
- for (int i = 0; i < GCIdleTimeHandler::kMaxNoProgressIdleTimesPerMode; i++) {
+ for (int i = 0; i < GCIdleTimeHandler::kMaxNoProgressIdleTimes; i++) {
GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
EXPECT_EQ(DO_NOTHING, action.type);
}
// Simulate incremental marking stopped and not eligible to start.
heap_state.incremental_marking_stopped = true;
- heap_state.can_start_incremental_marking = false;
double idle_time_ms = 10.0;
- for (int i = 0; i < GCIdleTimeHandler::kMaxNoProgressIdleTimesPerMode; i++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_NOTHING, action.type);
- }
- // We should return DONE after not making progress for some time.
+ // We should return DONE if we cannot start incremental marking.
GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
EXPECT_EQ(DONE, action.type);
}
-
-TEST_F(GCIdleTimeHandlerTest, BackgroundReduceLatencyToReduceMemory) {
- GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
- heap_state.incremental_marking_stopped = false;
- heap_state.can_start_incremental_marking = true;
- double idle_time_ms = GCIdleTimeHandler::kMinBackgroundIdleTime;
- handler()->NotifyScavenge();
- EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
- int limit =
- GCIdleTimeHandler::kBackgroundIdleNotificationsBeforeMutatorIsIdle;
- for (int i = 0; i < limit; i++) {
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- }
- handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, SkipUselessGCs) {
- GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
- heap_state.incremental_marking_stopped = false;
- heap_state.can_start_incremental_marking = true;
- TransitionToReduceMemoryMode(heap_state);
- EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
- double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
- GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
- handler()->NotifyMarkCompact(false);
- handler()->NotifyIdleMarkCompact();
- action = handler()->Compute(idle_time_ms, heap_state);
- EXPECT_EQ(DONE, action.type);
-}
-
} // namespace internal
} // namespace v8
--- /dev/null
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <limits>
+
+#include "src/flags.h"
+#include "src/heap/memory-reducer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace v8 {
+namespace internal {
+
+MemoryReducer::State DoneState() {
+ return MemoryReducer::State(MemoryReducer::kDone, 0, 0.0);
+}
+
+
+MemoryReducer::State WaitState(int started_gcs, double next_gc_start_ms) {
+ return MemoryReducer::State(MemoryReducer::kWait, started_gcs,
+ next_gc_start_ms);
+}
+
+
+MemoryReducer::State RunState(int started_gcs, double next_gc_start_ms) {
+ return MemoryReducer::State(MemoryReducer::kRun, started_gcs,
+ next_gc_start_ms);
+}
+
+
+MemoryReducer::Event MarkCompactEvent(double time_ms,
+ bool next_gc_likely_to_collect_more) {
+ MemoryReducer::Event event;
+ event.type = MemoryReducer::kMarkCompact;
+ event.time_ms = time_ms;
+ event.next_gc_likely_to_collect_more = next_gc_likely_to_collect_more;
+ return event;
+}
+
+
+MemoryReducer::Event MarkCompactEventGarbageLeft(double time_ms) {
+ return MarkCompactEvent(time_ms, true);
+}
+
+
+MemoryReducer::Event MarkCompactEventNoGarbageLeft(double time_ms) {
+ return MarkCompactEvent(time_ms, false);
+}
+
+
+MemoryReducer::Event TimerEvent(double time_ms, bool low_allocation_rate,
+ bool can_start_incremental_gc) {
+ MemoryReducer::Event event;
+ event.type = MemoryReducer::kTimer;
+ event.time_ms = time_ms;
+ event.low_allocation_rate = low_allocation_rate;
+ event.can_start_incremental_gc = can_start_incremental_gc;
+ return event;
+}
+
+
+MemoryReducer::Event TimerEventLowAllocationRate(double time_ms) {
+ return TimerEvent(time_ms, true, true);
+}
+
+
+MemoryReducer::Event TimerEventHighAllocationRate(double time_ms) {
+ return TimerEvent(time_ms, false, true);
+}
+
+
+MemoryReducer::Event TimerEventPendingGC(double time_ms) {
+ return TimerEvent(time_ms, true, false);
+}
+
+
+MemoryReducer::Event ContextDisposedEvent(double time_ms) {
+ MemoryReducer::Event event;
+ event.type = MemoryReducer::kContextDisposed;
+ event.time_ms = time_ms;
+ return event;
+}
+
+
+TEST(MemoryReducer, FromDoneToDone) {
+ MemoryReducer::State state0(DoneState()), state1(DoneState());
+
+ state1 = MemoryReducer::Step(state0, TimerEventLowAllocationRate(0));
+ EXPECT_EQ(MemoryReducer::kDone, state1.action);
+
+ state1 = MemoryReducer::Step(state0, TimerEventHighAllocationRate(0));
+ EXPECT_EQ(MemoryReducer::kDone, state1.action);
+
+ state1 = MemoryReducer::Step(state0, TimerEventPendingGC(0));
+ EXPECT_EQ(MemoryReducer::kDone, state1.action);
+}
+
+
+TEST(MemoryReducer, FromDoneToWait) {
+ if (!FLAG_incremental_marking) return;
+
+ MemoryReducer::State state0(DoneState()), state1(DoneState());
+
+ state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(0));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+ EXPECT_EQ(0, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(0));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+ EXPECT_EQ(0, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(state0, ContextDisposedEvent(0));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+ EXPECT_EQ(0, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromWaitToWait) {
+ if (!FLAG_incremental_marking) return;
+
+ MemoryReducer::State state0(WaitState(2, 1000.0)), state1(DoneState());
+
+ state1 = MemoryReducer::Step(state0, ContextDisposedEvent(2000));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(
+ state0, TimerEventLowAllocationRate(state0.next_gc_start_ms - 1));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(state0, TimerEventHighAllocationRate(2000));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(state0, TimerEventPendingGC(2000));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromWaitToRun) {
+ if (!FLAG_incremental_marking) return;
+
+ MemoryReducer::State state0(WaitState(0, 1000.0)), state1(DoneState());
+
+ state1 = MemoryReducer::Step(
+ state0, TimerEventLowAllocationRate(state0.next_gc_start_ms + 1));
+ EXPECT_EQ(MemoryReducer::kRun, state1.action);
+ EXPECT_EQ(0, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs + 1, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromRunToRun) {
+ if (!FLAG_incremental_marking) return;
+
+ MemoryReducer::State state0(RunState(1, 0.0)), state1(DoneState());
+
+ state1 = MemoryReducer::Step(state0, TimerEventLowAllocationRate(2000));
+ EXPECT_EQ(MemoryReducer::kRun, state1.action);
+ EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(state0, TimerEventHighAllocationRate(2000));
+ EXPECT_EQ(MemoryReducer::kRun, state1.action);
+ EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(state0, TimerEventPendingGC(2000));
+ EXPECT_EQ(MemoryReducer::kRun, state1.action);
+ EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+ state1 = MemoryReducer::Step(state0, ContextDisposedEvent(2000));
+ EXPECT_EQ(MemoryReducer::kRun, state1.action);
+ EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromRunToDone) {
+ if (!FLAG_incremental_marking) return;
+
+ MemoryReducer::State state0(RunState(2, 0.0)), state1(DoneState());
+
+ state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000));
+ EXPECT_EQ(MemoryReducer::kDone, state1.action);
+ EXPECT_EQ(0, state1.next_gc_start_ms);
+ EXPECT_EQ(0, state1.started_gcs);
+
+ state0.started_gcs = MemoryReducer::kMaxNumberOfGCs;
+
+ state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000));
+ EXPECT_EQ(MemoryReducer::kDone, state1.action);
+ EXPECT_EQ(0, state1.next_gc_start_ms);
+ EXPECT_EQ(0, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromRunToWait) {
+ if (!FLAG_incremental_marking) return;
+
+ MemoryReducer::State state0(RunState(2, 0.0)), state1(DoneState());
+
+ state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(2000 + MemoryReducer::kShortDelayMs, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+ state0.started_gcs = 1;
+
+ state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000));
+ EXPECT_EQ(MemoryReducer::kWait, state1.action);
+ EXPECT_EQ(2000 + MemoryReducer::kShortDelayMs, state1.next_gc_start_ms);
+ EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+}
+
+} // namespace internal
+} // namespace v8
'libplatform/task-queue-unittest.cc',
'libplatform/worker-thread-unittest.cc',
'heap/gc-idle-time-handler-unittest.cc',
+ 'heap/memory-reducer-unittest.cc',
'heap/heap-unittest.cc',
'run-all-unittests.cc',
'test-utils.h',
'../../src/heap-snapshot-generator-inl.h',
'../../src/heap-snapshot-generator.cc',
'../../src/heap-snapshot-generator.h',
+ '../../src/heap/memory-reducer.cc',
+ '../../src/heap/memory-reducer.h',
'../../src/heap/gc-idle-time-handler.cc',
'../../src/heap/gc-idle-time-handler.h',
'../../src/heap/gc-tracer.cc',