Reland "Replace reduce-memory mode in idle notification with delayed clean-up GC."
This reverts commit269918927a
. This reverts commit435b3c873a
. The failing test is fixing in chromium. BUG=chromium:490559 LOG=NO TBR=hpayer@chromium.org Review URL: https://codereview.chromium.org/1208993009 Cr-Commit-Position: refs/heads/master@{#29512}
This commit is contained in:
parent
8c298c79c2
commit
a7f62edb71
2
BUILD.gn
2
BUILD.gn
@ -857,6 +857,8 @@ source_set("v8_base") {
|
||||
"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",
|
||||
|
@ -50,7 +50,6 @@ void GCIdleTimeHandler::HeapState::Print() {
|
||||
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 ",
|
||||
@ -195,70 +194,15 @@ bool GCIdleTimeHandler::ShouldDoOverApproximateWeakClosure(
|
||||
|
||||
|
||||
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
|
||||
@ -267,26 +211,18 @@ GCIdleTimeAction GCIdleTimeHandler::Compute(double idle_time_in_ms,
|
||||
// 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();
|
||||
@ -307,14 +243,6 @@ GCIdleTimeAction GCIdleTimeHandler::Action(double idle_time_in_ms,
|
||||
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();
|
||||
@ -323,95 +251,16 @@ GCIdleTimeAction GCIdleTimeHandler::Action(double idle_time_in_ms,
|
||||
}
|
||||
}
|
||||
|
||||
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_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ class GCIdleTimeAction {
|
||||
result.type = DONE;
|
||||
result.parameter = 0;
|
||||
result.additional_work = false;
|
||||
result.reduce_memory = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -36,17 +35,14 @@ 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,
|
||||
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;
|
||||
}
|
||||
|
||||
@ -55,18 +51,14 @@ 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(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;
|
||||
}
|
||||
|
||||
@ -75,7 +67,6 @@ class GCIdleTimeAction {
|
||||
result.type = DO_FINALIZE_SWEEPING;
|
||||
result.parameter = 0;
|
||||
result.additional_work = false;
|
||||
result.reduce_memory = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -84,7 +75,6 @@ class GCIdleTimeAction {
|
||||
GCIdleTimeActionType type;
|
||||
intptr_t parameter;
|
||||
bool additional_work;
|
||||
bool reduce_memory;
|
||||
};
|
||||
|
||||
|
||||
@ -128,6 +118,8 @@ class GCIdleTimeHandler {
|
||||
// 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;
|
||||
@ -147,28 +139,10 @@ class GCIdleTimeHandler {
|
||||
|
||||
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:
|
||||
@ -178,7 +152,6 @@ class GCIdleTimeHandler {
|
||||
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;
|
||||
@ -191,26 +164,11 @@ class GCIdleTimeHandler {
|
||||
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);
|
||||
@ -239,36 +197,11 @@ class GCIdleTimeHandler {
|
||||
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);
|
||||
};
|
||||
|
@ -20,6 +20,7 @@
|
||||
#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"
|
||||
@ -107,8 +108,6 @@ Heap::Heap()
|
||||
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()),
|
||||
@ -144,6 +143,7 @@ Heap::Heap()
|
||||
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),
|
||||
@ -927,6 +927,11 @@ bool Heap::CollectGarbage(GarbageCollector collector, const char* gc_reason,
|
||||
}
|
||||
|
||||
bool next_gc_likely_to_collect_more = false;
|
||||
intptr_t committed_memory_before = 0;
|
||||
|
||||
if (collector == MARK_COMPACTOR) {
|
||||
committed_memory_before = CommittedOldGenerationMemory();
|
||||
}
|
||||
|
||||
{
|
||||
tracer()->Start(collector, gc_reason, collector_reason);
|
||||
@ -948,9 +953,20 @@ bool Heap::CollectGarbage(GarbageCollector collector, const char* gc_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);
|
||||
@ -985,10 +1001,20 @@ int Heap::NotifyContextDisposed(bool dependant_context) {
|
||||
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;
|
||||
@ -4763,6 +4789,21 @@ bool Heap::HasLowAllocationRate() {
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
@ -4789,7 +4830,6 @@ 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();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -4820,15 +4860,6 @@ GCIdleTimeHandler::HeapState Heap::ComputeHeapState() {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -4842,10 +4873,7 @@ bool Heap::PerformIdleTimeAction(GCIdleTimeAction action,
|
||||
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(
|
||||
@ -4866,17 +4894,9 @@ bool Heap::PerformIdleTimeAction(GCIdleTimeAction action,
|
||||
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:
|
||||
@ -5612,23 +5632,14 @@ void Heap::SetOldGenerationAllocationLimit(intptr_t old_gen_size,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#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"
|
||||
@ -832,6 +833,10 @@ class Heap {
|
||||
// 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) {
|
||||
@ -1618,6 +1623,10 @@ class Heap {
|
||||
// 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.
|
||||
|
||||
@ -1778,10 +1787,6 @@ class Heap {
|
||||
// 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_;
|
||||
@ -2258,7 +2263,6 @@ class Heap {
|
||||
|
||||
bool HasLowYoungGenerationAllocationRate();
|
||||
bool HasLowOldGenerationAllocationRate();
|
||||
bool HasLowAllocationRate();
|
||||
|
||||
void ReduceNewSpaceSize();
|
||||
|
||||
@ -2325,6 +2329,8 @@ class Heap {
|
||||
|
||||
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_;
|
||||
|
140
src/heap/memory-reducer.cc
Normal file
140
src/heap/memory-reducer.cc
Normal file
@ -0,0 +1,140 @@
|
||||
// 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
|
135
src/heap/memory-reducer.h
Normal file
135
src/heap/memory-reducer.h
Normal file
@ -0,0 +1,135 @@
|
||||
// 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
|
@ -15361,6 +15361,7 @@ static void CreateGarbageInOldSpace() {
|
||||
|
||||
// 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;
|
||||
@ -15371,6 +15372,9 @@ TEST(TestIdleNotification) {
|
||||
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)) +
|
||||
|
@ -25,7 +25,6 @@ class GCIdleTimeHandlerTest : public ::testing::Test {
|
||||
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;
|
||||
@ -38,63 +37,13 @@ class GCIdleTimeHandlerTest : public ::testing::Test {
|
||||
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_;
|
||||
@ -263,11 +212,8 @@ TEST_F(GCIdleTimeHandlerTest, ContextDisposeLowRate) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -278,12 +224,8 @@ TEST_F(GCIdleTimeHandlerTest, ContextDisposeHighRate) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -293,12 +235,8 @@ TEST_F(GCIdleTimeHandlerTest, AfterContextDisposeZeroIdleTime) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -307,16 +245,11 @@ TEST_F(GCIdleTimeHandlerTest, AfterContextDisposeSmallIdleTime1) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -328,12 +261,8 @@ TEST_F(GCIdleTimeHandlerTest, AfterContextDisposeSmallIdleTime2) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -341,197 +270,106 @@ TEST_F(GCIdleTimeHandlerTest, IncrementalMarking1) {
|
||||
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);
|
||||
}
|
||||
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, StopEventually1) {
|
||||
TEST_F(GCIdleTimeHandlerTest, DoNotStartIncrementalMarking) {
|
||||
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);
|
||||
double idle_time_ms = 10.0;
|
||||
GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
|
||||
EXPECT_EQ(DONE, action.type);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(GCIdleTimeHandlerTest, StopEventually3) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(GCIdleTimeHandlerTest, ContinueAfterStop1) {
|
||||
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);
|
||||
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, ContinueAfterStop2) {
|
||||
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);
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@ -547,7 +385,6 @@ TEST_F(GCIdleTimeHandlerTest, ZeroIdleTimeNothingToDo) {
|
||||
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);
|
||||
@ -555,105 +392,16 @@ TEST_F(GCIdleTimeHandlerTest, SmallIdleTimeNothingToDo) {
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
@ -669,50 +417,11 @@ TEST_F(GCIdleTimeHandlerTest, DoneIfNotMakingProgressOnIncrementalMarking) {
|
||||
|
||||
// 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
|
||||
|
237
test/unittests/heap/memory-reducer-unittest.cc
Normal file
237
test/unittests/heap/memory-reducer-unittest.cc
Normal file
@ -0,0 +1,237 @@
|
||||
// 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
|
@ -91,6 +91,7 @@
|
||||
'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',
|
||||
|
@ -677,6 +677,8 @@
|
||||
'../../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',
|
||||
|
Loading…
Reference in New Issue
Block a user