Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(20)

Side by Side Diff: src/compiler-dispatcher/compiler-dispatcher.cc

Issue 2606263002: Use background tasks for the compiler dispatcher (Closed)
Patch Set: unit tests Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 the V8 project authors. All rights reserved. 1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "src/compiler-dispatcher/compiler-dispatcher.h" 5 #include "src/compiler-dispatcher/compiler-dispatcher.h"
6 6
7 #include "include/v8-platform.h" 7 #include "include/v8-platform.h"
8 #include "include/v8.h" 8 #include "include/v8.h"
9 #include "src/base/platform/time.h" 9 #include "src/base/platform/time.h"
10 #include "src/cancelable-task.h" 10 #include "src/cancelable-task.h"
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
59 try_catch.ReThrow(); 59 try_catch.ReThrow();
60 } 60 }
61 return job->status() != CompileJobStatus::kFailed; 61 return job->status() != CompileJobStatus::kFailed;
62 } 62 }
63 63
64 bool IsFinished(CompilerDispatcherJob* job) { 64 bool IsFinished(CompilerDispatcherJob* job) {
65 return job->status() == CompileJobStatus::kDone || 65 return job->status() == CompileJobStatus::kDone ||
66 job->status() == CompileJobStatus::kFailed; 66 job->status() == CompileJobStatus::kFailed;
67 } 67 }
68 68
69 bool CanRunOnAnyThread(CompilerDispatcherJob* job) {
70 return (job->status() == CompileJobStatus::kReadyToParse &&
71 job->can_parse_on_background_thread()) ||
72 (job->status() == CompileJobStatus::kReadyToCompile &&
73 job->can_compile_on_background_thread());
74 }
75
76 void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) {
77 DCHECK(CanRunOnAnyThread(job));
78 switch (job->status()) {
79 case CompileJobStatus::kReadyToParse:
80 job->Parse();
81 break;
82
83 case CompileJobStatus::kReadyToCompile:
84 job->Compile();
85 break;
86
87 default:
88 UNREACHABLE();
89 }
90 }
91
69 // Theoretically we get 50ms of idle time max, however it's unlikely that 92 // Theoretically we get 50ms of idle time max, however it's unlikely that
70 // we'll get all of it so try to be a conservative. 93 // we'll get all of it so try to be a conservative.
71 const double kMaxIdleTimeToExpectInMs = 40; 94 const double kMaxIdleTimeToExpectInMs = 40;
72 95
73 } // namespace 96 } // namespace
74 97
98 class CompilerDispatcher::BackgroundTask : public CancelableTask {
99 public:
100 BackgroundTask(Isolate* isolate, CompilerDispatcher* dispatcher);
101 ~BackgroundTask() override;
102
103 // CancelableTask implementation.
104 void RunInternal() override;
105
106 private:
107 CompilerDispatcher* dispatcher_;
108
109 DISALLOW_COPY_AND_ASSIGN(BackgroundTask);
110 };
111
112 CompilerDispatcher::BackgroundTask::BackgroundTask(
113 Isolate* isolate, CompilerDispatcher* dispatcher)
114 : CancelableTask(isolate), dispatcher_(dispatcher) {}
115
116 CompilerDispatcher::BackgroundTask::~BackgroundTask() {}
117
118 void CompilerDispatcher::BackgroundTask::RunInternal() {
119 dispatcher_->DoBackgroundWork();
120 }
121
75 class CompilerDispatcher::IdleTask : public CancelableIdleTask { 122 class CompilerDispatcher::IdleTask : public CancelableIdleTask {
76 public: 123 public:
77 IdleTask(Isolate* isolate, CompilerDispatcher* dispatcher); 124 IdleTask(Isolate* isolate, CompilerDispatcher* dispatcher);
78 ~IdleTask() override; 125 ~IdleTask() override;
79 126
80 // CancelableIdleTask implementation. 127 // CancelableIdleTask implementation.
81 void RunInternal(double deadline_in_seconds) override; 128 void RunInternal(double deadline_in_seconds) override;
82 129
83 private: 130 private:
84 CompilerDispatcher* dispatcher_; 131 CompilerDispatcher* dispatcher_;
(...skipping 10 matching lines...) Expand all
95 void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) { 142 void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) {
96 dispatcher_->DoIdleWork(deadline_in_seconds); 143 dispatcher_->DoIdleWork(deadline_in_seconds);
97 } 144 }
98 145
99 CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform, 146 CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
100 size_t max_stack_size) 147 size_t max_stack_size)
101 : isolate_(isolate), 148 : isolate_(isolate),
102 platform_(platform), 149 platform_(platform),
103 max_stack_size_(max_stack_size), 150 max_stack_size_(max_stack_size),
104 tracer_(new CompilerDispatcherTracer(isolate_)), 151 tracer_(new CompilerDispatcherTracer(isolate_)),
105 idle_task_scheduled_(false) {} 152 idle_task_scheduled_(false),
153 num_scheduled_background_tasks_(0),
154 main_thread_blocking_on_job_(nullptr) {}
106 155
107 CompilerDispatcher::~CompilerDispatcher() { 156 CompilerDispatcher::~CompilerDispatcher() {
108 // To avoid crashing in unit tests due to unfished jobs. 157 // To avoid crashing in unit tests due to unfished jobs.
109 AbortAll(BlockingBehavior::kBlock); 158 AbortAll(BlockingBehavior::kBlock);
110 } 159 }
111 160
112 bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) { 161 bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) {
113 if (!IsEnabled()) return false; 162 if (!IsEnabled()) return false;
114 163
115 // We only handle functions (no eval / top-level code / wasm) that are 164 // We only handle functions (no eval / top-level code / wasm) that are
(...skipping 15 matching lines...) Expand all
131 180
132 bool CompilerDispatcher::IsEnabled() const { 181 bool CompilerDispatcher::IsEnabled() const {
133 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); 182 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
134 return FLAG_compiler_dispatcher && platform_->IdleTasksEnabled(v8_isolate); 183 return FLAG_compiler_dispatcher && platform_->IdleTasksEnabled(v8_isolate);
135 } 184 }
136 185
137 bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const { 186 bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const {
138 return GetJobFor(function) != jobs_.end(); 187 return GetJobFor(function) != jobs_.end();
139 } 188 }
140 189
190 void CompilerDispatcher::EnsureNotOnBackground(CompilerDispatcherJob* job) {
marja 2017/01/03 10:42:41 This function name is confusing. I had to read it
jochen (gone - plz use gerrit) 2017/01/03 12:59:35 done
191 base::LockGuard<base::Mutex> lock(&mutex_);
192 if (running_background_jobs_.find(job) == running_background_jobs_.end()) {
193 pending_background_jobs_.erase(job);
194 return;
195 }
196 DCHECK_NULL(main_thread_blocking_on_job_);
197 main_thread_blocking_on_job_ = job;
198 while (main_thread_blocking_on_job_ != nullptr) {
199 main_thread_blocking_signal_.Wait(&mutex_);
200 }
201 DCHECK(pending_background_jobs_.find(job) == pending_background_jobs_.end());
vogelheim 2017/01/03 10:32:37 Also the same DCHECK(...), with running_background
jochen (gone - plz use gerrit) 2017/01/03 12:59:36 done
202 }
203
141 bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) { 204 bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
142 JobMap::const_iterator job = GetJobFor(function); 205 JobMap::const_iterator job = GetJobFor(function);
143 CHECK(job != jobs_.end()); 206 CHECK(job != jobs_.end());
144 207
145 // TODO(jochen): Check if there's an in-flight background task working on this 208 EnsureNotOnBackground(job->second.get());
146 // job.
147 while (!IsFinished(job->second.get())) { 209 while (!IsFinished(job->second.get())) {
148 DoNextStepOnMainThread(isolate_, job->second.get(), 210 DoNextStepOnMainThread(isolate_, job->second.get(),
149 ExceptionHandling::kThrow); 211 ExceptionHandling::kThrow);
150 } 212 }
151 bool result = job->second->status() != CompileJobStatus::kFailed; 213 bool result = job->second->status() != CompileJobStatus::kFailed;
152 job->second->ResetOnMainThread(); 214 job->second->ResetOnMainThread();
153 jobs_.erase(job); 215 jobs_.erase(job);
154 return result; 216 return result;
155 } 217 }
156 218
157 void CompilerDispatcher::Abort(Handle<SharedFunctionInfo> function,
158 BlockingBehavior blocking) {
159 USE(blocking);
160 JobMap::const_iterator job = GetJobFor(function);
161 CHECK(job != jobs_.end());
162
163 // TODO(jochen): Check if there's an in-flight background task working on this
164 // job.
165 job->second->ResetOnMainThread();
166 jobs_.erase(job);
167 }
168
169 void CompilerDispatcher::AbortAll(BlockingBehavior blocking) { 219 void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
170 USE(blocking); 220 // TODO(jochen): Implement support for non-blocking abort.
171 // TODO(jochen): Check if there's an in-flight background task working on this 221 DCHECK(blocking == BlockingBehavior::kBlock);
vogelheim 2017/01/03 10:32:37 DCHECK_EQ ?
jochen (gone - plz use gerrit) 2017/01/03 12:59:36 doesn't work with enum classes
172 // job.
173 for (auto& kv : jobs_) { 222 for (auto& kv : jobs_) {
223 EnsureNotOnBackground(kv.second.get());
174 kv.second->ResetOnMainThread(); 224 kv.second->ResetOnMainThread();
175 } 225 }
176 jobs_.clear(); 226 jobs_.clear();
177 } 227 }
178 228
179 CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor( 229 CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
180 Handle<SharedFunctionInfo> shared) const { 230 Handle<SharedFunctionInfo> shared) const {
181 if (!shared->script()->IsScript()) return jobs_.end(); 231 if (!shared->script()->IsScript()) return jobs_.end();
182 std::pair<int, int> key(Script::cast(shared->script())->id(), 232 std::pair<int, int> key(Script::cast(shared->script())->id(),
183 shared->function_literal_id()); 233 shared->function_literal_id());
184 auto range = jobs_.equal_range(key); 234 auto range = jobs_.equal_range(key);
185 for (auto job = range.first; job != range.second; ++job) { 235 for (auto job = range.first; job != range.second; ++job) {
186 if (job->second->IsAssociatedWith(shared)) return job; 236 if (job->second->IsAssociatedWith(shared)) return job;
187 } 237 }
188 return jobs_.end(); 238 return jobs_.end();
189 } 239 }
190 240
191 void CompilerDispatcher::ScheduleIdleTaskIfNeeded() { 241 void CompilerDispatcher::ScheduleIdleTaskFromAnyThread() {
192 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); 242 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
193 DCHECK(platform_->IdleTasksEnabled(v8_isolate)); 243 DCHECK(platform_->IdleTasksEnabled(v8_isolate));
194 if (idle_task_scheduled_) return; 244 {
195 if (jobs_.empty()) return; 245 base::LockGuard<base::Mutex> lock(&mutex_);
196 idle_task_scheduled_ = true; 246 if (idle_task_scheduled_) return;
247 idle_task_scheduled_ = true;
248 }
197 platform_->CallIdleOnForegroundThread(v8_isolate, 249 platform_->CallIdleOnForegroundThread(v8_isolate,
198 new IdleTask(isolate_, this)); 250 new IdleTask(isolate_, this));
199 } 251 }
200 252
253 void CompilerDispatcher::ScheduleIdleTaskIfNeeded() {
254 if (jobs_.empty()) return;
255 ScheduleIdleTaskFromAnyThread();
256 }
257
258 void CompilerDispatcher::ConsiderJobForBackgroundProcessing(
259 CompilerDispatcherJob* job) {
260 if (!CanRunOnAnyThread(job)) return;
261 {
262 base::LockGuard<base::Mutex> lock(&mutex_);
263 pending_background_jobs_.insert(job);
264 }
265 ScheduleMoreBackgroundTasksIfNeeded();
266 }
267
268 void CompilerDispatcher::ScheduleMoreBackgroundTasksIfNeeded() {
269 if (FLAG_single_threaded) return;
vogelheim 2017/01/03 10:32:37 Hmm. So if --single_threaded, ConsiderJobForBackgr
jochen (gone - plz use gerrit) 2017/01/03 12:59:36 if the job is too big for idle time. Since the DoI
270 {
271 base::LockGuard<base::Mutex> lock(&mutex_);
272 if (pending_background_jobs_.empty()) return;
273 if (platform_->NumberOfAvailableBackgroundThreads() <=
274 num_scheduled_background_tasks_) {
275 return;
276 }
277 ++num_scheduled_background_tasks_;
278 }
279 platform_->CallOnBackgroundThread(new BackgroundTask(isolate_, this),
280 v8::Platform::kShortRunningTask);
281 }
282
283 void CompilerDispatcher::DoBackgroundWork() {
284 CompilerDispatcherJob* job = nullptr;
marja 2017/01/03 10:43:41 Can you add an assert (somewhere, maybe here) that
jochen (gone - plz use gerrit) 2017/01/03 12:59:35 that assert will not necessarily hold: the embedde
marja 2017/01/03 13:14:57 Hmm, I'm missing something... ScheduleMoreBackgro
jochen (gone - plz use gerrit) 2017/01/03 14:01:44 assume that each task counts down num_scheduled_ba
marja 2017/01/03 14:22:45 Ah I see It's slightly unintuitive though that nu
285 {
286 base::LockGuard<base::Mutex> lock(&mutex_);
287 --num_scheduled_background_tasks_;
288 if (!pending_background_jobs_.empty()) {
289 auto it = pending_background_jobs_.begin();
290 job = *it;
291 pending_background_jobs_.erase(it);
292 running_background_jobs_.insert(job);
293 }
294 }
295 if (job == nullptr) return;
296 DoNextStepOnBackgroundThread(job);
297 {
298 base::LockGuard<base::Mutex> lock(&mutex_);
299 running_background_jobs_.erase(job);
300
301 if (main_thread_blocking_on_job_ == job) {
302 main_thread_blocking_on_job_ = nullptr;
303 main_thread_blocking_signal_.NotifyOne();
304 }
305 }
306 ScheduleMoreBackgroundTasksIfNeeded();
307 ScheduleIdleTaskFromAnyThread();
vogelheim 2017/01/03 10:32:37 ScheduleIdleTaskFromAnyThread unconditionally sche
jochen (gone - plz use gerrit) 2017/01/03 12:59:36 correct. Added a comment
308 }
309
201 void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { 310 void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
202 idle_task_scheduled_ = false; 311 {
312 base::LockGuard<base::Mutex> lock(&mutex_);
313 idle_task_scheduled_ = false;
314 }
203 315
204 // Number of jobs that are unlikely to make progress during any idle callback 316 // Number of jobs that are unlikely to make progress during any idle callback
205 // due to their estimated duration. 317 // due to their estimated duration.
206 size_t too_long_jobs = 0; 318 size_t too_long_jobs = 0;
207 319
208 // Iterate over all available jobs & remaining time. For each job, decide 320 // Iterate over all available jobs & remaining time. For each job, decide
209 // whether to 1) skip it (if it would take too long), 2) erase it (if it's 321 // whether to 1) skip it (if it would take too long), 2) erase it (if it's
210 // finished), or 3) make progress on it. 322 // finished), or 3) make progress on it.
211 double idle_time_in_seconds = 323 double idle_time_in_seconds =
212 deadline_in_seconds - platform_->MonotonicallyIncreasingTime(); 324 deadline_in_seconds - platform_->MonotonicallyIncreasingTime();
213 for (auto job = jobs_.begin(); 325 for (auto job = jobs_.begin();
214 job != jobs_.end() && idle_time_in_seconds > 0.0; 326 job != jobs_.end() && idle_time_in_seconds > 0.0;
215 idle_time_in_seconds = 327 idle_time_in_seconds =
216 deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) { 328 deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) {
329 {
330 // Don't work on jobs that are being worked on by background tasks.
331 // Similarly, remove jobs we work on from the set of available background
332 // jobs.
333 base::LockGuard<base::Mutex> lock(&mutex_);
334 if (running_background_jobs_.find(job->second.get()) !=
335 running_background_jobs_.end()) {
336 ++job;
337 continue;
338 }
339 auto it = pending_background_jobs_.find(job->second.get());
marja 2017/01/03 10:42:41 This is kinda weird... here we erase the job from
jochen (gone - plz use gerrit) 2017/01/03 12:59:36 we're always working on it in the sense that we in
marja 2017/01/03 13:14:57 If the job is too big (or the amount of idle time
jochen (gone - plz use gerrit) 2017/01/03 14:01:44 what about this?
340 if (it != pending_background_jobs_.end()) {
341 pending_background_jobs_.erase(it);
342 }
343 }
217 double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs(); 344 double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs();
218 if (idle_time_in_seconds < 345 if (idle_time_in_seconds <
219 (estimate_in_ms / 346 (estimate_in_ms /
220 static_cast<double>(base::Time::kMillisecondsPerSecond))) { 347 static_cast<double>(base::Time::kMillisecondsPerSecond))) {
221 // If there's not enough time left, try to estimate whether we would 348 // If there's not enough time left, try to estimate whether we would
222 // have managed to finish the job in a large idle task to assess 349 // have managed to finish the job in a large idle task to assess
223 // whether we should ask for another idle callback. 350 // whether we should ask for another idle callback.
224 if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs; 351 if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs;
352 ConsiderJobForBackgroundProcessing(job->second.get());
marja 2017/01/03 10:42:40 This is somewhat surprising... so we only push tho
jochen (gone - plz use gerrit) 2017/01/03 12:59:36 the other cases are: - the job is finished (no ne
marja 2017/01/03 13:14:57 But if there are a zillion of small tasks and seve
jochen (gone - plz use gerrit) 2017/01/03 14:01:44 added a lengthy comment
225 ++job; 353 ++job;
226 } else if (IsFinished(job->second.get())) { 354 } else if (IsFinished(job->second.get())) {
227 job->second->ResetOnMainThread(); 355 job->second->ResetOnMainThread();
228 job = jobs_.erase(job); 356 job = jobs_.erase(job);
229 break; 357 break;
230 } else { 358 } else {
231 // Do one step, and keep processing the job (as we don't advance the 359 // Do one step, and keep processing the job (as we don't advance the
232 // iterator). 360 // iterator).
233 DoNextStepOnMainThread(isolate_, job->second.get(), 361 DoNextStepOnMainThread(isolate_, job->second.get(),
234 ExceptionHandling::kSwallow); 362 ExceptionHandling::kSwallow);
235 } 363 }
236 } 364 }
237 if (jobs_.size() > too_long_jobs) ScheduleIdleTaskIfNeeded(); 365 if (jobs_.size() > too_long_jobs) ScheduleIdleTaskIfNeeded();
238 } 366 }
239 367
240 } // namespace internal 368 } // namespace internal
241 } // namespace v8 369 } // namespace v8
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698