1 | // Copyright 2015 the V8 project authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #include "src/heap/memory-reducer.h" |
6 | |
7 | #include "src/flags.h" |
8 | #include "src/heap/gc-tracer.h" |
9 | #include "src/heap/heap-inl.h" |
10 | #include "src/heap/incremental-marking.h" |
11 | #include "src/utils.h" |
12 | #include "src/v8.h" |
13 | |
14 | namespace v8 { |
15 | namespace internal { |
16 | |
17 | const int MemoryReducer::kLongDelayMs = 8000; |
18 | const int MemoryReducer::kShortDelayMs = 500; |
19 | const int MemoryReducer::kWatchdogDelayMs = 100000; |
20 | const int MemoryReducer::kMaxNumberOfGCs = 3; |
21 | const double MemoryReducer::kCommittedMemoryFactor = 1.1; |
22 | const size_t MemoryReducer::kCommittedMemoryDelta = 10 * MB; |
23 | |
24 | MemoryReducer::MemoryReducer(Heap* heap) |
25 | : heap_(heap), |
26 | taskrunner_(V8::GetCurrentPlatform()->GetForegroundTaskRunner( |
27 | reinterpret_cast<v8::Isolate*>(heap->isolate()))), |
28 | state_(kDone, 0, 0.0, 0.0, 0), |
29 | js_calls_counter_(0), |
30 | js_calls_sample_time_ms_(0.0) {} |
31 | |
32 | MemoryReducer::TimerTask::TimerTask(MemoryReducer* memory_reducer) |
33 | : CancelableTask(memory_reducer->heap()->isolate()), |
34 | memory_reducer_(memory_reducer) {} |
35 | |
36 | |
37 | void MemoryReducer::TimerTask::RunInternal() { |
38 | Heap* heap = memory_reducer_->heap(); |
39 | Event event; |
40 | double time_ms = heap->MonotonicallyIncreasingTimeInMs(); |
41 | heap->tracer()->SampleAllocation(time_ms, heap->NewSpaceAllocationCounter(), |
42 | heap->OldGenerationAllocationCounter()); |
43 | bool low_allocation_rate = heap->HasLowAllocationRate(); |
44 | bool optimize_for_memory = heap->ShouldOptimizeForMemoryUsage(); |
45 | if (FLAG_trace_gc_verbose) { |
46 | heap->isolate()->PrintWithTimestamp( |
47 | "Memory reducer: %s, %s\n" , |
48 | low_allocation_rate ? "low alloc" : "high alloc" , |
49 | optimize_for_memory ? "background" : "foreground" ); |
50 | } |
51 | event.type = kTimer; |
52 | event.time_ms = time_ms; |
53 | // The memory reducer will start incremental markig if |
54 | // 1) mutator is likely idle: js call rate is low and allocation rate is low. |
55 | // 2) mutator is in background: optimize for memory flag is set. |
56 | event.should_start_incremental_gc = |
57 | low_allocation_rate || optimize_for_memory; |
58 | event.can_start_incremental_gc = |
59 | heap->incremental_marking()->IsStopped() && |
60 | (heap->incremental_marking()->CanBeActivated() || optimize_for_memory); |
61 | event.committed_memory = heap->CommittedOldGenerationMemory(); |
62 | memory_reducer_->NotifyTimer(event); |
63 | } |
64 | |
65 | |
66 | void MemoryReducer::NotifyTimer(const Event& event) { |
67 | DCHECK_EQ(kTimer, event.type); |
68 | DCHECK_EQ(kWait, state_.action); |
69 | state_ = Step(state_, event); |
70 | if (state_.action == kRun) { |
71 | DCHECK(heap()->incremental_marking()->IsStopped()); |
72 | DCHECK(FLAG_incremental_marking); |
73 | if (FLAG_trace_gc_verbose) { |
74 | heap()->isolate()->PrintWithTimestamp("Memory reducer: started GC #%d\n" , |
75 | state_.started_gcs); |
76 | } |
77 | heap()->StartIdleIncrementalMarking( |
78 | GarbageCollectionReason::kMemoryReducer, |
79 | kGCCallbackFlagCollectAllExternalMemory); |
80 | } else if (state_.action == kWait) { |
81 | if (!heap()->incremental_marking()->IsStopped() && |
82 | heap()->ShouldOptimizeForMemoryUsage()) { |
83 | // Make progress with pending incremental marking if memory usage has |
84 | // higher priority than latency. This is important for background tabs |
85 | // that do not send idle notifications. |
86 | const int kIncrementalMarkingDelayMs = 500; |
87 | double deadline = heap()->MonotonicallyIncreasingTimeInMs() + |
88 | kIncrementalMarkingDelayMs; |
89 | heap()->incremental_marking()->AdvanceWithDeadline( |
90 | deadline, IncrementalMarking::NO_GC_VIA_STACK_GUARD, |
91 | StepOrigin::kTask); |
92 | heap()->FinalizeIncrementalMarkingIfComplete( |
93 | GarbageCollectionReason::kFinalizeMarkingViaTask); |
94 | } |
95 | // Re-schedule the timer. |
96 | ScheduleTimer(state_.next_gc_start_ms - event.time_ms); |
97 | if (FLAG_trace_gc_verbose) { |
98 | heap()->isolate()->PrintWithTimestamp( |
99 | "Memory reducer: waiting for %.f ms\n" , |
100 | state_.next_gc_start_ms - event.time_ms); |
101 | } |
102 | } |
103 | } |
104 | |
105 | |
106 | void MemoryReducer::NotifyMarkCompact(const Event& event) { |
107 | DCHECK_EQ(kMarkCompact, event.type); |
108 | Action old_action = state_.action; |
109 | state_ = Step(state_, event); |
110 | if (old_action != kWait && state_.action == kWait) { |
111 | // If we are transitioning to the WAIT state, start the timer. |
112 | ScheduleTimer(state_.next_gc_start_ms - event.time_ms); |
113 | } |
114 | if (old_action == kRun) { |
115 | if (FLAG_trace_gc_verbose) { |
116 | heap()->isolate()->PrintWithTimestamp( |
117 | "Memory reducer: finished GC #%d (%s)\n" , state_.started_gcs, |
118 | state_.action == kWait ? "will do more" : "done" ); |
119 | } |
120 | } |
121 | } |
122 | |
123 | void MemoryReducer::NotifyPossibleGarbage(const Event& event) { |
124 | DCHECK_EQ(kPossibleGarbage, event.type); |
125 | Action old_action = state_.action; |
126 | state_ = Step(state_, event); |
127 | if (old_action != kWait && state_.action == kWait) { |
128 | // If we are transitioning to the WAIT state, start the timer. |
129 | ScheduleTimer(state_.next_gc_start_ms - event.time_ms); |
130 | } |
131 | } |
132 | |
133 | |
134 | bool MemoryReducer::WatchdogGC(const State& state, const Event& event) { |
135 | return state.last_gc_time_ms != 0 && |
136 | event.time_ms > state.last_gc_time_ms + kWatchdogDelayMs; |
137 | } |
138 | |
139 | |
140 | // For specification of this function see the comment for MemoryReducer class. |
141 | MemoryReducer::State MemoryReducer::Step(const State& state, |
142 | const Event& event) { |
143 | if (!FLAG_incremental_marking || !FLAG_memory_reducer) { |
144 | return State(kDone, 0, 0, state.last_gc_time_ms, 0); |
145 | } |
146 | switch (state.action) { |
147 | case kDone: |
148 | if (event.type == kTimer) { |
149 | return state; |
150 | } else if (event.type == kMarkCompact) { |
151 | if (event.committed_memory < |
152 | Max(static_cast<size_t>(state.committed_memory_at_last_run * |
153 | kCommittedMemoryFactor), |
154 | state.committed_memory_at_last_run + kCommittedMemoryDelta)) { |
155 | return state; |
156 | } else { |
157 | return State(kWait, 0, event.time_ms + kLongDelayMs, |
158 | event.type == kMarkCompact ? event.time_ms |
159 | : state.last_gc_time_ms, |
160 | 0); |
161 | } |
162 | } else { |
163 | DCHECK_EQ(kPossibleGarbage, event.type); |
164 | return State( |
165 | kWait, 0, event.time_ms + kLongDelayMs, |
166 | event.type == kMarkCompact ? event.time_ms : state.last_gc_time_ms, |
167 | 0); |
168 | } |
169 | case kWait: |
170 | switch (event.type) { |
171 | case kPossibleGarbage: |
172 | return state; |
173 | case kTimer: |
174 | if (state.started_gcs >= kMaxNumberOfGCs) { |
175 | return State(kDone, kMaxNumberOfGCs, 0.0, state.last_gc_time_ms, |
176 | event.committed_memory); |
177 | } else if (event.can_start_incremental_gc && |
178 | (event.should_start_incremental_gc || |
179 | WatchdogGC(state, event))) { |
180 | if (state.next_gc_start_ms <= event.time_ms) { |
181 | return State(kRun, state.started_gcs + 1, 0.0, |
182 | state.last_gc_time_ms, 0); |
183 | } else { |
184 | return state; |
185 | } |
186 | } else { |
187 | return State(kWait, state.started_gcs, event.time_ms + kLongDelayMs, |
188 | state.last_gc_time_ms, 0); |
189 | } |
190 | case kMarkCompact: |
191 | return State(kWait, state.started_gcs, event.time_ms + kLongDelayMs, |
192 | event.time_ms, 0); |
193 | } |
194 | case kRun: |
195 | if (event.type != kMarkCompact) { |
196 | return state; |
197 | } else { |
198 | if (state.started_gcs < kMaxNumberOfGCs && |
199 | (event.next_gc_likely_to_collect_more || state.started_gcs == 1)) { |
200 | return State(kWait, state.started_gcs, event.time_ms + kShortDelayMs, |
201 | event.time_ms, 0); |
202 | } else { |
203 | return State(kDone, kMaxNumberOfGCs, 0.0, event.time_ms, |
204 | event.committed_memory); |
205 | } |
206 | } |
207 | } |
208 | UNREACHABLE(); |
209 | } |
210 | |
211 | void MemoryReducer::ScheduleTimer(double delay_ms) { |
212 | DCHECK_LT(0, delay_ms); |
213 | if (heap()->IsTearingDown()) return; |
214 | // Leave some room for precision error in task scheduler. |
215 | const double kSlackMs = 100; |
216 | taskrunner_->PostDelayedTask( |
217 | base::make_unique<MemoryReducer::TimerTask>(this), |
218 | (delay_ms + kSlackMs) / 1000.0); |
219 | } |
220 | |
221 | void MemoryReducer::TearDown() { state_ = State(kDone, 0, 0, 0.0, 0); } |
222 | |
223 | } // namespace internal |
224 | } // namespace v8 |
225 | |