// 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(
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)
return static_cast<size_t>(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<size_t>(gc_tracer->MarkCompactSpeedInBytesPerMillisecond());
+ if (idle_time_in_ms >=
+ static_cast<int>(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<size_t>(EstimateMarkingStepSize(idle_time_in_ms, speed));
+ return GCIdleTimeAction::IncrementalMarking(step_size);
+}
}
}
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;
// 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);
};
return static_cast<intptr_t>(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<intptr_t>(bytes / durations);
+}
}
} // namespace v8::internal
#ifndef V8_HEAP_GC_TRACER_H_
#define V8_HEAP_GC_TRACER_H_
+#include "src/base/platform/platform.h"
+
namespace v8 {
namespace internal {
// 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,
// 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.
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),
gc_state_ = NOT_IN_GC;
- scavenges_since_last_idle_round_++;
+ gc_idle_time_handler_.NotifyScavenge();
}
}
-void Heap::AdvanceIdleIncrementalMarking(int idle_time_in_ms) {
- intptr_t step_size =
- static_cast<size_t>(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);
}
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();
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<size_t>(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;
}
#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"
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<int>(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);
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_;
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.
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<size_t>::max();
+ size_t speed = 1;
+ size_t time = GCIdleTimeHandler::EstimateMarkCompactTime(size, speed);
+ EXPECT_EQ(GCIdleTimeHandler::kMaxMarkCompactTimeInMs, time);
+}
+
} // namespace internal
} // namespace v8