Commit 7a1b3a7b authored by jochen's avatar jochen Committed by Commit bot

Use background tasks for the compiler dispatcher

BUG=v8:5215
R=marja@chromium.org,vogelheim@chromium.org

Review-Url: https://codereview.chromium.org/2606263002
Cr-Commit-Position: refs/heads/master@{#42035}
parent 72c37076
......@@ -93,13 +93,18 @@ void CancelableTaskManager::CancelAndWait() {
}
}
CancelableTask::CancelableTask(Isolate* isolate)
: Cancelable(isolate->cancelable_task_manager()), isolate_(isolate) {}
: CancelableTask(isolate, isolate->cancelable_task_manager()) {}
CancelableTask::CancelableTask(Isolate* isolate, CancelableTaskManager* manager)
: Cancelable(manager), isolate_(isolate) {}
CancelableIdleTask::CancelableIdleTask(Isolate* isolate)
: Cancelable(isolate->cancelable_task_manager()), isolate_(isolate) {}
: CancelableIdleTask(isolate, isolate->cancelable_task_manager()) {}
CancelableIdleTask::CancelableIdleTask(Isolate* isolate,
CancelableTaskManager* manager)
: Cancelable(manager), isolate_(isolate) {}
} // namespace internal
} // namespace v8
......@@ -126,6 +126,7 @@ class V8_EXPORT_PRIVATE Cancelable {
class CancelableTask : public Cancelable, public Task {
public:
explicit CancelableTask(Isolate* isolate);
CancelableTask(Isolate* isolate, CancelableTaskManager* manager);
// Task overrides.
void Run() final {
......@@ -148,6 +149,7 @@ class CancelableTask : public Cancelable, public Task {
class CancelableIdleTask : public Cancelable, public IdleTask {
public:
explicit CancelableIdleTask(Isolate* isolate);
CancelableIdleTask(Isolate* isolate, CancelableTaskManager* manager);
// IdleTask overrides.
void Run(double deadline_in_seconds) final {
......
......@@ -7,9 +7,12 @@
#include <map>
#include <memory>
#include <unordered_set>
#include <utility>
#include "src/base/macros.h"
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h"
#include "src/globals.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
......@@ -19,6 +22,7 @@ class Platform;
namespace internal {
class CancelableTaskManager;
class CompilerDispatcherJob;
class CompilerDispatcherTracer;
class Isolate;
......@@ -29,6 +33,30 @@ class Handle;
// The CompilerDispatcher uses a combination of idle tasks and background tasks
// to parse and compile lazily parsed functions.
//
// As both parsing and compilation currently requires a preparation and
// finalization step that happens on the main thread, every task has to be
// advanced during idle time first. Depending on the properties of the task, it
// can then be parsed or compiled on either background threads, or during idle
// time. Last, it has to be finalized during idle time again.
//
// CompilerDispatcher::jobs_ maintains the list of all CompilerDispatcherJobs
// the CompilerDispatcher knows about.
//
// CompilerDispatcher::pending_background_jobs_ contains the set of
// CompilerDispatcherJobs that can be processed on a background thread.
//
// CompilerDispatcher::running_background_jobs_ contains the set of
// CompilerDispatcherJobs that are currently being processed on a background
// thread.
//
// CompilerDispatcher::DoIdleWork tries to advance as many jobs out of jobs_ as
// possible during idle time. If a job can't be advanced, but is suitable for
// background processing, it fires off background threads.
//
// CompilerDispatcher::DoBackgroundWork advances one of the pending jobs, and
// then spins of another idle task to potentially do the final step on the main
// thread.
class V8_EXPORT_PRIVATE CompilerDispatcher {
public:
enum class BlockingBehavior { kBlock, kDontBlock };
......@@ -55,15 +83,23 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
private:
FRIEND_TEST(CompilerDispatcherTest, IdleTaskSmallIdleTime);
FRIEND_TEST(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread);
FRIEND_TEST(IgnitionCompilerDispatcherTest, FinishNowWithBackgroundTask);
typedef std::multimap<std::pair<int, int>,
std::unique_ptr<CompilerDispatcherJob>>
JobMap;
class BackgroundTask;
class IdleTask;
void WaitForJobIfRunningOnBackground(CompilerDispatcherJob* job);
bool IsEnabled() const;
JobMap::const_iterator GetJobFor(Handle<SharedFunctionInfo> shared) const;
void ConsiderJobForBackgroundProcessing(CompilerDispatcherJob* job);
void ScheduleMoreBackgroundTasksIfNeeded();
void ScheduleIdleTaskFromAnyThread();
void ScheduleIdleTaskIfNeeded();
void DoBackgroundWork();
void DoIdleWork(double deadline_in_seconds);
Isolate* isolate_;
......@@ -71,12 +107,33 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
size_t max_stack_size_;
std::unique_ptr<CompilerDispatcherTracer> tracer_;
bool idle_task_scheduled_;
std::unique_ptr<CancelableTaskManager> task_manager_;
// Mapping from (script id, function literal id) to job. We use a multimap,
// as script id is not necessarily unique.
JobMap jobs_;
// The following members can be accessed from any thread. Methods need to hold
// the mutex |mutex_| while accessing them.
base::Mutex mutex_;
bool idle_task_scheduled_;
// Number of currently scheduled BackgroundTask objects.
size_t num_scheduled_background_tasks_;
// The set of CompilerDispatcherJobs that can be advanced on any thread.
std::unordered_set<CompilerDispatcherJob*> pending_background_jobs_;
// The set of CompilerDispatcherJobs currently processed on background
// threads.
std::unordered_set<CompilerDispatcherJob*> running_background_jobs_;
// If not nullptr, then the main thread waits for the task processing
// this job, and blocks on the ConditionVariable main_thread_blocking_signal_.
CompilerDispatcherJob* main_thread_blocking_on_job_;
base::ConditionVariable main_thread_blocking_signal_;
DISALLOW_COPY_AND_ASSIGN(CompilerDispatcher);
};
......
......@@ -5,10 +5,13 @@
#include "src/compiler-dispatcher/compiler-dispatcher.h"
#include "include/v8-platform.h"
#include "src/base/platform/semaphore.h"
#include "src/compiler-dispatcher/compiler-dispatcher-job.h"
#include "src/compiler-dispatcher/compiler-dispatcher-tracer.h"
#include "src/flags.h"
#include "src/handles.h"
#include "src/objects-inl.h"
#include "src/v8.h"
#include "test/unittests/compiler-dispatcher/compiler-dispatcher-helper.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -40,16 +43,44 @@ class CompilerDispatcherTest : public TestWithContext {
bool CompilerDispatcherTest::old_flag_;
class IgnitionCompilerDispatcherTest : public CompilerDispatcherTest {
public:
IgnitionCompilerDispatcherTest() = default;
~IgnitionCompilerDispatcherTest() override = default;
static void SetUpTestCase() {
old_flag_ = i::FLAG_ignition;
i::FLAG_ignition = true;
CompilerDispatcherTest::SetUpTestCase();
}
static void TearDownTestCase() {
CompilerDispatcherTest::TearDownTestCase();
i::FLAG_ignition = old_flag_;
}
private:
static bool old_flag_;
DISALLOW_COPY_AND_ASSIGN(IgnitionCompilerDispatcherTest);
};
bool IgnitionCompilerDispatcherTest::old_flag_;
namespace {
class MockPlatform : public v8::Platform {
public:
MockPlatform() : task_(nullptr), time_(0.0), time_step_(0.0) {}
~MockPlatform() override = default;
MockPlatform() : idle_task_(nullptr), time_(0.0), time_step_(0.0), sem_(0) {}
~MockPlatform() override {
EXPECT_TRUE(tasks_.empty());
EXPECT_TRUE(idle_task_ == nullptr);
}
size_t NumberOfAvailableBackgroundThreads() override { return 1; }
void CallOnBackgroundThread(Task* task,
ExpectedRuntime expected_runtime) override {
UNREACHABLE();
tasks_.push_back(task);
}
void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override {
......@@ -63,7 +94,8 @@ class MockPlatform : public v8::Platform {
void CallIdleOnForegroundThread(v8::Isolate* isolate,
IdleTask* task) override {
task_ = task;
ASSERT_TRUE(idle_task_ == nullptr);
idle_task_ = task;
}
bool IdleTasksEnabled(v8::Isolate* isolate) override { return true; }
......@@ -74,21 +106,78 @@ class MockPlatform : public v8::Platform {
}
void RunIdleTask(double deadline_in_seconds, double time_step) {
ASSERT_TRUE(task_ != nullptr);
ASSERT_TRUE(idle_task_ != nullptr);
time_step_ = time_step;
IdleTask* task = task_;
task_ = nullptr;
IdleTask* task = idle_task_;
idle_task_ = nullptr;
task->Run(deadline_in_seconds);
delete task;
}
bool IdleTaskPending() const { return !!task_; }
bool IdleTaskPending() const { return idle_task_; }
bool BackgroundTasksPending() const { return !tasks_.empty(); }
void RunBackgroundTasksAndBlock(Platform* platform) {
std::vector<Task*> tasks;
tasks.swap(tasks_);
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, true),
kShortRunningTask);
sem_.Wait();
}
void RunBackgroundTasks(Platform* platform) {
std::vector<Task*> tasks;
tasks.swap(tasks_);
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, false),
kShortRunningTask);
}
void ClearBackgroundTasks() {
std::vector<Task*> tasks;
tasks.swap(tasks_);
for (auto& task : tasks) {
delete task;
}
}
void ClearIdleTask() {
ASSERT_TRUE(idle_task_ != nullptr);
delete idle_task_;
idle_task_ = nullptr;
}
private:
IdleTask* task_;
class TaskWrapper : public Task {
public:
TaskWrapper(MockPlatform* platform, const std::vector<Task*>& tasks,
bool signal)
: platform_(platform), tasks_(tasks), signal_(signal) {}
~TaskWrapper() = default;
void Run() override {
for (auto& task : tasks_) {
task->Run();
delete task;
}
if (signal_) platform_->sem_.Signal();
}
private:
MockPlatform* platform_;
std::vector<Task*> tasks_;
bool signal_;
DISALLOW_COPY_AND_ASSIGN(TaskWrapper);
};
IdleTask* idle_task_;
double time_;
double time_step_;
std::vector<Task*> tasks_;
base::Semaphore sem_;
DISALLOW_COPY_AND_ASSIGN(MockPlatform);
};
......@@ -112,8 +201,10 @@ TEST_F(CompilerDispatcherTest, IsEnqueued) {
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
dispatcher.Abort(shared, CompilerDispatcher::BlockingBehavior::kBlock);
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kBlock);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, FinishNow) {
......@@ -132,6 +223,8 @@ TEST_F(CompilerDispatcherTest, FinishNow) {
// Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, IdleTask) {
......@@ -188,7 +281,7 @@ TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) {
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
CompileJobStatus::kReadyToParse);
// Only grant a lot of idle time and freeze time.
// Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
......@@ -225,5 +318,91 @@ TEST_F(CompilerDispatcherTest, IdleTaskException) {
ASSERT_FALSE(try_catch.HasCaught());
}
TEST_F(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] =
"function g() { var y = 1; function f6(x) { return x * y }; return f6; } "
"g();";
Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script));
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
CompileJobStatus::kInitial);
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
CompileJobStatus::kReadyToCompile);
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.BackgroundTasksPending());
platform.RunBackgroundTasksAndBlock(V8::GetCurrentPlatform());
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.BackgroundTasksPending());
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
CompileJobStatus::kCompiled);
// Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
}
TEST_F(IgnitionCompilerDispatcherTest, FinishNowWithBackgroundTask) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] =
"function g() { var y = 1; function f7(x) { return x * y }; return f7; } "
"g();";
Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script));
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
CompileJobStatus::kInitial);
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
CompileJobStatus::kReadyToCompile);
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.BackgroundTasksPending());
// This does not block, but races with the FinishNow() call below.
platform.RunBackgroundTasks(V8::GetCurrentPlatform());
ASSERT_TRUE(dispatcher.FinishNow(shared));
// Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_FALSE(platform.BackgroundTasksPending());
}
} // namespace internal
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment