From 53fdf75be1fb551f34036981b3aac70adb365068 Mon Sep 17 00:00:00 2001 From: "ulan@chromium.org" Date: Thu, 21 Aug 2014 14:42:22 +0000 Subject: [PATCH] Move idle notification handling to GCIdleTimeHandler. BUG= R=hpayer@chromium.org Review URL: https://codereview.chromium.org/492763002 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@23282 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/heap/gc-idle-time-handler.cc | 55 ++++++++++++++++-- src/heap/gc-idle-time-handler.h | 92 +++++++++++++++++++++++++++++- src/heap/gc-tracer.cc | 17 ++++++ src/heap/gc-tracer.h | 10 +++- src/heap/heap.cc | 106 ++++++++++------------------------- src/heap/heap.h | 35 ++---------- test/heap-unittests/heap-unittest.cc | 24 ++++++++ 7 files changed, 224 insertions(+), 115 deletions(-) diff --git a/src/heap/gc-idle-time-handler.cc b/src/heap/gc-idle-time-handler.cc index 5ff4017..a2e08b2 100644 --- a/src/heap/gc-idle-time-handler.cc +++ b/src/heap/gc-idle-time-handler.cc @@ -3,12 +3,14 @@ // found in the LICENSE file. #include "src/heap/gc-idle-time-handler.h" +#include "src/heap/gc-tracer.h" +#include "src/utils.h" namespace v8 { namespace internal { - const double GCIdleTimeHandler::kConservativeTimeRatio = 0.9; +const size_t GCIdleTimeHandler::kMaxMarkCompactTimeInMs = 1000000; size_t GCIdleTimeHandler::EstimateMarkingStepSize( @@ -16,14 +18,13 @@ size_t GCIdleTimeHandler::EstimateMarkingStepSize( DCHECK(idle_time_in_ms > 0); if (marking_speed_in_bytes_per_ms == 0) { - marking_speed_in_bytes_per_ms = - GCIdleTimeHandler::kInitialConservativeMarkingSpeed; + marking_speed_in_bytes_per_ms = kInitialConservativeMarkingSpeed; } size_t marking_step_size = marking_speed_in_bytes_per_ms * idle_time_in_ms; if (marking_step_size / marking_speed_in_bytes_per_ms != idle_time_in_ms) { // In the case of an overflow we return maximum marking step size. - return GCIdleTimeHandler::kMaximumMarkingStepSize; + return kMaximumMarkingStepSize; } if (marking_step_size > kMaximumMarkingStepSize) @@ -32,5 +33,51 @@ size_t GCIdleTimeHandler::EstimateMarkingStepSize( return static_cast(marking_step_size * GCIdleTimeHandler::kConservativeTimeRatio); } + + +size_t GCIdleTimeHandler::EstimateMarkCompactTime( + size_t size_of_objects, size_t mark_compact_speed_in_bytes_per_ms) { + if (mark_compact_speed_in_bytes_per_ms == 0) { + mark_compact_speed_in_bytes_per_ms = kInitialConservativeMarkCompactSpeed; + } + size_t result = size_of_objects / mark_compact_speed_in_bytes_per_ms; + return Min(result, kMaxMarkCompactTimeInMs); +} + + +GCIdleTimeAction GCIdleTimeHandler::Compute(int idle_time_in_ms, + int contexts_disposed, + size_t size_of_objects, + bool incremental_marking_stopped, + GCTracer* gc_tracer) { + if (IsIdleRoundFinished()) { + if (EnoughGarbageSinceLastIdleRound() || contexts_disposed > 0) { + StartIdleRound(); + } else { + return GCIdleTimeAction::Nothing(); + } + } + if (incremental_marking_stopped) { + size_t speed = + static_cast(gc_tracer->MarkCompactSpeedInBytesPerMillisecond()); + if (idle_time_in_ms >= + static_cast(EstimateMarkCompactTime(size_of_objects, speed))) { + // If there are no more than two GCs left in this idle round and we are + // allowed to do a full GC, then make those GCs full in order to compact + // the code space. + // TODO(ulan): Once we enable code compaction for incremental marking, we + // can get rid of this special case and always start incremental marking. + int remaining_mark_sweeps = + kMaxMarkCompactsInIdleRound - mark_compacts_since_idle_round_started_; + if (contexts_disposed > 0 || remaining_mark_sweeps <= 2) { + return GCIdleTimeAction::FullGC(); + } + } + } + intptr_t speed = gc_tracer->IncrementalMarkingSpeedInBytesPerMillisecond(); + size_t step_size = + static_cast(EstimateMarkingStepSize(idle_time_in_ms, speed)); + return GCIdleTimeAction::IncrementalMarking(step_size); +} } } diff --git a/src/heap/gc-idle-time-handler.h b/src/heap/gc-idle-time-handler.h index 609fdb1..76cdb9f 100644 --- a/src/heap/gc-idle-time-handler.h +++ b/src/heap/gc-idle-time-handler.h @@ -10,13 +10,51 @@ namespace v8 { namespace internal { +enum GCIdleTimeActionType { + DO_NOTHING, + DO_INCREMENTAL_MARKING, + DO_SCAVENGE, + DO_FULL_GC +}; + + +class GCIdleTimeAction { + public: + static GCIdleTimeAction Nothing() { + GCIdleTimeAction result; + result.type = DO_NOTHING; + result.parameter = 0; + return result; + } + static GCIdleTimeAction IncrementalMarking(intptr_t step_size) { + GCIdleTimeAction result; + result.type = DO_INCREMENTAL_MARKING; + result.parameter = step_size; + return result; + } + static GCIdleTimeAction Scavenge() { + GCIdleTimeAction result; + result.type = DO_SCAVENGE; + result.parameter = 0; + return result; + } + static GCIdleTimeAction FullGC() { + GCIdleTimeAction result; + result.type = DO_FULL_GC; + result.parameter = 0; + return result; + } + + GCIdleTimeActionType type; + intptr_t parameter; +}; + +class GCTracer; + // The idle time handler makes decisions about which garbage collection // operations are executing during IdleNotification. class GCIdleTimeHandler { public: - static size_t EstimateMarkingStepSize(size_t idle_time_in_ms, - size_t marking_speed_in_bytes_per_ms); - // If we haven't recorded any incremental marking events yet, we carefully // mark with a conservative lower bound for the marking speed. static const size_t kInitialConservativeMarkingSpeed = 100 * KB; @@ -28,7 +66,55 @@ class GCIdleTimeHandler { // idle_time_in_ms. Hence, we conservatively prune our workload estimate. static const double kConservativeTimeRatio; + // If we haven't recorded any mark-compact events yet, we use + // conservative lower bound for the mark-compact speed. + static const size_t kInitialConservativeMarkCompactSpeed = 2 * MB; + + // Maximum mark-compact time returned by EstimateMarkCompactTime. + static const size_t kMaxMarkCompactTimeInMs; + + GCIdleTimeHandler() + : mark_compacts_since_idle_round_started_(0), + scavenges_since_last_idle_round_(0) {} + + GCIdleTimeAction Compute(int idle_time_in_ms, int contexts_disposed, + size_t size_of_objects, + bool incremental_marking_stopped, + GCTracer* gc_tracer); + + void NotifyIdleMarkCompact() { + if (mark_compacts_since_idle_round_started_ < kMaxMarkCompactsInIdleRound) { + ++mark_compacts_since_idle_round_started_; + if (mark_compacts_since_idle_round_started_ == + kMaxMarkCompactsInIdleRound) { + scavenges_since_last_idle_round_ = 0; + } + } + } + + void NotifyScavenge() { ++scavenges_since_last_idle_round_; } + + static size_t EstimateMarkingStepSize(size_t idle_time_in_ms, + size_t marking_speed_in_bytes_per_ms); + + static size_t EstimateMarkCompactTime( + size_t size_of_objects, size_t mark_compact_speed_in_bytes_per_ms); + private: + void StartIdleRound() { mark_compacts_since_idle_round_started_ = 0; } + bool IsIdleRoundFinished() { + return mark_compacts_since_idle_round_started_ == + kMaxMarkCompactsInIdleRound; + } + bool EnoughGarbageSinceLastIdleRound() { + return scavenges_since_last_idle_round_ >= kIdleScavengeThreshold; + } + + static const int kMaxMarkCompactsInIdleRound = 7; + static const int kIdleScavengeThreshold = 5; + int mark_compacts_since_idle_round_started_; + int scavenges_since_last_idle_round_; + DISALLOW_COPY_AND_ASSIGN(GCIdleTimeHandler); }; diff --git a/src/heap/gc-tracer.cc b/src/heap/gc-tracer.cc index a0f1c46..035e3ee 100644 --- a/src/heap/gc-tracer.cc +++ b/src/heap/gc-tracer.cc @@ -418,5 +418,22 @@ intptr_t GCTracer::ScavengeSpeedInBytesPerMillisecond() const { return static_cast(bytes / durations); } + + +intptr_t GCTracer::MarkCompactSpeedInBytesPerMillisecond() const { + intptr_t bytes = 0; + double durations = 0.0; + EventBuffer::const_iterator iter = mark_compactor_events_.begin(); + while (iter != mark_compactor_events_.end()) { + bytes += iter->start_object_size; + durations += iter->end_time - iter->start_time + + iter->pure_incremental_marking_duration; + ++iter; + } + + if (durations == 0.0) return 0; + + return static_cast(bytes / durations); +} } } // namespace v8::internal diff --git a/src/heap/gc-tracer.h b/src/heap/gc-tracer.h index fd53d3c..0524f25 100644 --- a/src/heap/gc-tracer.h +++ b/src/heap/gc-tracer.h @@ -5,6 +5,8 @@ #ifndef V8_HEAP_GC_TRACER_H_ #define V8_HEAP_GC_TRACER_H_ +#include "src/base/platform/platform.h" + namespace v8 { namespace internal { @@ -81,9 +83,9 @@ class RingBuffer { // GCTracer collects and prints ONE line after each garbage collector // invocation IFF --trace_gc is used. // TODO(ernstm): Unit tests. -class GCTracer BASE_EMBEDDED { +class GCTracer { public: - class Scope BASE_EMBEDDED { + class Scope { public: enum ScopeId { EXTERNAL, @@ -291,6 +293,10 @@ class GCTracer BASE_EMBEDDED { // Returns 0 if no events have been recorded. intptr_t ScavengeSpeedInBytesPerMillisecond() const; + // Compute the max mark-sweep speed in bytes/millisecond. + // Returns 0 if no events have been recorded. + intptr_t MarkCompactSpeedInBytesPerMillisecond() const; + private: // Print one detailed trace line in name=value format. // TODO(ernstm): Move to Heap. diff --git a/src/heap/heap.cc b/src/heap/heap.cc index 559f761..7604664 100644 --- a/src/heap/heap.cc +++ b/src/heap/heap.cc @@ -120,12 +120,7 @@ Heap::Heap() store_buffer_(this), marking_(this), incremental_marking_(this), - number_idle_notifications_(0), - last_idle_notification_gc_count_(0), - last_idle_notification_gc_count_init_(false), - mark_sweeps_since_idle_round_started_(0), gc_count_at_last_idle_gc_(0), - scavenges_since_last_idle_round_(kIdleScavengeThreshold), full_codegen_bytes_generated_(0), crankshaft_codegen_bytes_generated_(0), gcs_since_last_deopt_(0), @@ -1559,7 +1554,7 @@ void Heap::Scavenge() { gc_state_ = NOT_IN_GC; - scavenges_since_last_idle_round_++; + gc_idle_time_handler_.NotifyScavenge(); } @@ -4265,12 +4260,7 @@ void Heap::MakeHeapIterable() { } -void Heap::AdvanceIdleIncrementalMarking(int idle_time_in_ms) { - intptr_t step_size = - static_cast(GCIdleTimeHandler::EstimateMarkingStepSize( - idle_time_in_ms, - tracer_.IncrementalMarkingSpeedInBytesPerMillisecond())); - +void Heap::AdvanceIdleIncrementalMarking(intptr_t step_size) { incremental_marking()->Step(step_size, IncrementalMarking::NO_GC_VIA_STACK_GUARD, true); @@ -4283,7 +4273,7 @@ void Heap::AdvanceIdleIncrementalMarking(int idle_time_in_ms) { } CollectAllGarbage(kReduceMemoryFootprintMask, "idle notification: finalize incremental"); - mark_sweeps_since_idle_round_started_++; + gc_idle_time_handler_.NotifyIdleMarkCompact(); gc_count_at_last_idle_gc_ = gc_count_; if (uncommit) { new_space_.Shrink(); @@ -4296,83 +4286,49 @@ void Heap::AdvanceIdleIncrementalMarking(int idle_time_in_ms) { bool Heap::IdleNotification(int idle_time_in_ms) { // If incremental marking is off, we do not perform idle notification. if (!FLAG_incremental_marking) return true; - - // Minimal hint that allows to do full GC. - const int kMinHintForFullGC = 100; isolate()->counters()->gc_idle_time_allotted_in_ms()->AddSample( idle_time_in_ms); HistogramTimerScope idle_notification_scope( isolate_->counters()->gc_idle_notification()); - if (contexts_disposed_ > 0) { - contexts_disposed_ = 0; - int mark_sweep_time = Min(TimeMarkSweepWouldTakeInMs(), 1000); - if (idle_time_in_ms >= mark_sweep_time && !FLAG_expose_gc && - incremental_marking()->IsStopped()) { + GCIdleTimeAction action = gc_idle_time_handler_.Compute( + idle_time_in_ms, contexts_disposed_, static_cast(SizeOfObjects()), + incremental_marking()->IsStopped(), tracer()); + contexts_disposed_ = 0; + bool result = false; + switch (action.type) { + case DO_INCREMENTAL_MARKING: + if (incremental_marking()->IsStopped()) { + incremental_marking()->Start(); + } + AdvanceIdleIncrementalMarking(action.parameter); + break; + case DO_FULL_GC: { HistogramTimerScope scope(isolate_->counters()->gc_context()); - CollectAllGarbage(kReduceMemoryFootprintMask, - "idle notification: contexts disposed"); - } else { - AdvanceIdleIncrementalMarking(idle_time_in_ms); - } - - // After context disposal there is likely a lot of garbage remaining, reset - // the idle notification counters in order to trigger more incremental GCs - // on subsequent idle notifications. - StartIdleRound(); - return false; - } - - // By doing small chunks of GC work in each IdleNotification, - // perform a round of incremental GCs and after that wait until - // the mutator creates enough garbage to justify a new round. - // An incremental GC progresses as follows: - // 1. many incremental marking steps, - // 2. one old space mark-sweep-compact, - // Use mark-sweep-compact events to count incremental GCs in a round. - - if (mark_sweeps_since_idle_round_started_ >= kMaxMarkSweepsInIdleRound) { - if (EnoughGarbageSinceLastIdleRound()) { - StartIdleRound(); - } else { - return true; - } - } - - int remaining_mark_sweeps = - kMaxMarkSweepsInIdleRound - mark_sweeps_since_idle_round_started_; - - if (incremental_marking()->IsStopped()) { - // If there are no more than two GCs left in this idle round and we are - // allowed to do a full GC, then make those GCs full in order to compact - // the code space. - // TODO(ulan): Once we enable code compaction for incremental marking, - // we can get rid of this special case and always start incremental marking. - if (remaining_mark_sweeps <= 2 && idle_time_in_ms >= kMinHintForFullGC) { - CollectAllGarbage(kReduceMemoryFootprintMask, - "idle notification: finalize idle round"); - mark_sweeps_since_idle_round_started_++; - } else { - incremental_marking()->Start(); + const char* message = contexts_disposed_ + ? "idle notification: contexts disposed" + : "idle notification: finalize idle round"; + CollectAllGarbage(kReduceMemoryFootprintMask, message); + gc_idle_time_handler_.NotifyIdleMarkCompact(); + break; } + case DO_SCAVENGE: + CollectGarbage(NEW_SPACE, "idle notification: scavenge"); + break; + case DO_NOTHING: + result = true; + break; } - if (!incremental_marking()->IsStopped()) { - AdvanceIdleIncrementalMarking(idle_time_in_ms); - } - - if (mark_sweeps_since_idle_round_started_ >= kMaxMarkSweepsInIdleRound) { - FinishIdleRound(); - return true; - } - // If the IdleNotifcation is called with a large hint we will wait for // the sweepter threads here. + // TODO(ulan): move this in GCIdleTimeHandler. + const int kMinHintForFullGC = 100; if (idle_time_in_ms >= kMinHintForFullGC && mark_compact_collector()->sweeping_in_progress()) { mark_compact_collector()->EnsureSweepingCompleted(); } - return false; + return result; } diff --git a/src/heap/heap.h b/src/heap/heap.h index 5075b05..6886ddd 100644 --- a/src/heap/heap.h +++ b/src/heap/heap.h @@ -11,6 +11,7 @@ #include "src/assert-scope.h" #include "src/counters.h" #include "src/globals.h" +#include "src/heap/gc-idle-time-handler.h" #include "src/heap/gc-tracer.h" #include "src/heap/incremental-marking.h" #include "src/heap/mark-compact.h" @@ -1929,30 +1930,7 @@ class Heap { void SelectScavengingVisitorsTable(); - void StartIdleRound() { mark_sweeps_since_idle_round_started_ = 0; } - - void FinishIdleRound() { - mark_sweeps_since_idle_round_started_ = kMaxMarkSweepsInIdleRound; - scavenges_since_last_idle_round_ = 0; - } - - bool EnoughGarbageSinceLastIdleRound() { - return (scavenges_since_last_idle_round_ >= kIdleScavengeThreshold); - } - - // Estimates how many milliseconds a Mark-Sweep would take to complete. - // In idle notification handler we assume that this function will return: - // - a number less than 10 for small heaps, which are less than 8Mb. - // - a number greater than 10 for large heaps, which are greater than 32Mb. - int TimeMarkSweepWouldTakeInMs() { - // Rough estimate of how many megabytes of heap can be processed in 1 ms. - static const int kMbPerMs = 2; - - int heap_size_mb = static_cast(SizeOfObjects() / MB); - return heap_size_mb / kMbPerMs; - } - - void AdvanceIdleIncrementalMarking(int idle_time_in_ms); + void AdvanceIdleIncrementalMarking(intptr_t step_size); void ClearObjectStats(bool clear_last_time_stats = false); @@ -2005,13 +1983,8 @@ class Heap { IncrementalMarking incremental_marking_; - int number_idle_notifications_; - unsigned int last_idle_notification_gc_count_; - bool last_idle_notification_gc_count_init_; - - int mark_sweeps_since_idle_round_started_; + GCIdleTimeHandler gc_idle_time_handler_; unsigned int gc_count_at_last_idle_gc_; - int scavenges_since_last_idle_round_; // These two counters are monotomically increasing and never reset. size_t full_codegen_bytes_generated_; @@ -2029,7 +2002,7 @@ class Heap { static const int kAllocationSiteScratchpadSize = 256; int allocation_sites_scratchpad_length_; - static const int kMaxMarkSweepsInIdleRound = 7; + static const int kMaxMarkCompactsInIdleRound = 7; static const int kIdleScavengeThreshold = 5; // Shared state read by the scavenge collector and set by ScavengeObject. diff --git a/test/heap-unittests/heap-unittest.cc b/test/heap-unittests/heap-unittest.cc index db331f9..f51908b 100644 --- a/test/heap-unittests/heap-unittest.cc +++ b/test/heap-unittests/heap-unittest.cc @@ -45,5 +45,29 @@ TEST(EstimateMarkingStepSizeTest, EstimateMarkingStepSizeOverflow2) { step_size); } + +TEST(EstimateMarkCompactTimeTest, EstimateMarkCompactTimeInitial) { + size_t size = 100 * MB; + size_t time = GCIdleTimeHandler::EstimateMarkCompactTime(size, 0); + EXPECT_EQ(size / GCIdleTimeHandler::kInitialConservativeMarkCompactSpeed, + time); +} + + +TEST(EstimateMarkCompactTimeTest, EstimateMarkCompactTimeNonZero) { + size_t size = 100 * MB; + size_t speed = 10 * KB; + size_t time = GCIdleTimeHandler::EstimateMarkCompactTime(size, speed); + EXPECT_EQ(size / speed, time); +} + + +TEST(EstimateMarkCompactTimeTest, EstimateMarkCompactTimeMax) { + size_t size = std::numeric_limits::max(); + size_t speed = 1; + size_t time = GCIdleTimeHandler::EstimateMarkCompactTime(size, speed); + EXPECT_EQ(GCIdleTimeHandler::kMaxMarkCompactTimeInMs, time); +} + } // namespace internal } // namespace v8 -- 2.7.4